From 7872bd5a443ea2abf37c96556170e1dd400ed370 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 6 Jul 2021 21:49:42 +0300 Subject: [PATCH 1/4] p2p-circuit v2 (#125) * v2 client scaffolding * gomod: go-libp2p-core and go-libp2p-transport-upgrader feature dependencies * Conn implements network.ConnStat * add reservation stub * utilities * dial scaffolding and v1 compat dialing * stream handling scaffolding and v1 incoming connection handling * implement hop tagging * export timeout variables * v2 protobuf * v2 client protocol implementation * implement Reserve * go get go-libp2p-swarm@feat/transient-conns * implement client.New * rework pb status codes * client responds with UNEXPECTED_MESSAGE when it's actually an unexpected message * relay scaffolding, reservation implementation * implement relaying * implement missing details * add options for resources/limit * gc idle conn counts * fix clown shoes in cancellation check * end to end relay test * untag peers with expired reservations * add time limit test * better debug log for accepted conns * add data limit test * add v2-v1 compatibility tests * godocs * add WithACL relay option * only return public relay addrs in reservation record * remove the refresh restriction madness * set default limit Data to 128K * fix typo in AllowReserve godoc * fix some small issues - remove context from constructor - remove stream handler when closing the host - remove the awkward cancellation check from handleStream * fix tests * address review comments - Add deadline for Reserve calls - Add deadline for dials - Add some comments for things that confuse aarsh. * humor aarsh and add initializers for slices * comment nitpicks * fix bug in slice pre-allocations * add deadline to connectV1 * make Relay.Close thread-safe * untag peers with reservations when closing the relay * gomod: get go-libp2p-asn-util * add IP/ASN reservation constraints * gomod: update deps * fix e2e test * increase default limit duration to 2min * update protocol for vouched relay addrs; provide absolute expiration time instead of TTL * update for reservation changes * add voucher to the reservation pb * TODO about reservation vouchers * deduplicate protocol ID definitions between relay and client * add reservation vouchers * emit and consume reservation vouchers * improve limit data test * deduplicate concurrent relay dials to the samke peer * improve dialer deduplication * add a short timeout to dialing the relay in order to aid deduplication * gomod: fix go1.16 madness * spec compliance: don't include p2p-circuit in reservation addrs * spec compliance: refuse reservation and connection attempts over relayed connections * test shim: add empty file in test directory * spec compliance: update protobuf * spec compliance: use libp2p envelopes for reservation vouchers * fix staticcheck Co-authored-by: Marten Seemann --- examples/go.sum | 3 +- examples/ipfs-camp-2019/go.sum | 3 +- examples/pubsub/chat/go.sum | 2 + go.mod | 4 + go.sum | 4 + p2p/host/circuitv2/client/client.go | 66 + p2p/host/circuitv2/client/conn.go | 145 ++ p2p/host/circuitv2/client/dial.go | 239 +++ p2p/host/circuitv2/client/handlers.go | 172 +++ p2p/host/circuitv2/client/listen.go | 54 + p2p/host/circuitv2/client/reservation.go | 122 ++ p2p/host/circuitv2/client/transport.go | 80 + p2p/host/circuitv2/pb/Makefile | 11 + p2p/host/circuitv2/pb/circuit.pb.go | 1766 ++++++++++++++++++++++ p2p/host/circuitv2/pb/circuit.proto | 60 + p2p/host/circuitv2/pb/voucher.pb.go | 438 ++++++ p2p/host/circuitv2/pb/voucher.proto | 9 + p2p/host/circuitv2/proto/protocol.go | 7 + p2p/host/circuitv2/proto/voucher.go | 72 + p2p/host/circuitv2/proto/voucher_test.go | 68 + p2p/host/circuitv2/relay/acl.go | 17 + p2p/host/circuitv2/relay/ipcs.go | 110 ++ p2p/host/circuitv2/relay/options.go | 27 + p2p/host/circuitv2/relay/relay.go | 522 +++++++ p2p/host/circuitv2/relay/resources.go | 62 + p2p/host/circuitv2/test/compat_test.go | 177 +++ p2p/host/circuitv2/test/e2e_test.go | 361 +++++ p2p/host/circuitv2/test/empty.go | 1 + p2p/host/circuitv2/test/ipcs_test.go | 69 + p2p/host/circuitv2/util/io.go | 67 + p2p/host/circuitv2/util/ma.go | 10 + p2p/host/circuitv2/util/pbconv.go | 97 ++ 32 files changed, 4843 insertions(+), 2 deletions(-) create mode 100644 p2p/host/circuitv2/client/client.go create mode 100644 p2p/host/circuitv2/client/conn.go create mode 100644 p2p/host/circuitv2/client/dial.go create mode 100644 p2p/host/circuitv2/client/handlers.go create mode 100644 p2p/host/circuitv2/client/listen.go create mode 100644 p2p/host/circuitv2/client/reservation.go create mode 100644 p2p/host/circuitv2/client/transport.go create mode 100644 p2p/host/circuitv2/pb/Makefile create mode 100644 p2p/host/circuitv2/pb/circuit.pb.go create mode 100644 p2p/host/circuitv2/pb/circuit.proto create mode 100644 p2p/host/circuitv2/pb/voucher.pb.go create mode 100644 p2p/host/circuitv2/pb/voucher.proto create mode 100644 p2p/host/circuitv2/proto/protocol.go create mode 100644 p2p/host/circuitv2/proto/voucher.go create mode 100644 p2p/host/circuitv2/proto/voucher_test.go create mode 100644 p2p/host/circuitv2/relay/acl.go create mode 100644 p2p/host/circuitv2/relay/ipcs.go create mode 100644 p2p/host/circuitv2/relay/options.go create mode 100644 p2p/host/circuitv2/relay/relay.go create mode 100644 p2p/host/circuitv2/relay/resources.go create mode 100644 p2p/host/circuitv2/test/compat_test.go create mode 100644 p2p/host/circuitv2/test/e2e_test.go create mode 100644 p2p/host/circuitv2/test/empty.go create mode 100644 p2p/host/circuitv2/test/ipcs_test.go create mode 100644 p2p/host/circuitv2/util/io.go create mode 100644 p2p/host/circuitv2/util/ma.go create mode 100644 p2p/host/circuitv2/util/pbconv.go diff --git a/examples/go.sum b/examples/go.sum index fa6e8eeb38..d5f816cef1 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -429,8 +429,9 @@ github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZ github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= -github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052 h1:BM7aaOF7RpmNn9+9g6uTjGJ0cTzWr5j9i9IKeun2M8U= github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052/go.mod h1:nRMRTab+kZuk0LnKZpxhOVH/ndsdr2Nr//Zltc/vwgo= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20210818120414-1f382a4aa43a h1:6yEuCOY31elgeJ2KA2JiREZjIznvH6lOWCdHRuhgEgc= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20210818120414-1f382a4aa43a/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-autonat v0.4.2 h1:YMp7StMi2dof+baaxkbxaizXjY1RPvU71CXfxExzcUU= github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= github.com/libp2p/go-libp2p-blankhost v0.2.0 h1:3EsGAi0CBGcZ33GwRuXEYJLLPoVWyXJ1bcJzAJjINkk= diff --git a/examples/ipfs-camp-2019/go.sum b/examples/ipfs-camp-2019/go.sum index 7453b4e417..8f1f154f88 100644 --- a/examples/ipfs-camp-2019/go.sum +++ b/examples/ipfs-camp-2019/go.sum @@ -429,8 +429,9 @@ github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZ github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= -github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052 h1:BM7aaOF7RpmNn9+9g6uTjGJ0cTzWr5j9i9IKeun2M8U= github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052/go.mod h1:nRMRTab+kZuk0LnKZpxhOVH/ndsdr2Nr//Zltc/vwgo= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20210818120414-1f382a4aa43a h1:6yEuCOY31elgeJ2KA2JiREZjIznvH6lOWCdHRuhgEgc= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20210818120414-1f382a4aa43a/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-autonat v0.4.2 h1:YMp7StMi2dof+baaxkbxaizXjY1RPvU71CXfxExzcUU= github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= github.com/libp2p/go-libp2p-blankhost v0.2.0 h1:3EsGAi0CBGcZ33GwRuXEYJLLPoVWyXJ1bcJzAJjINkk= diff --git a/examples/pubsub/chat/go.sum b/examples/pubsub/chat/go.sum index 3256e2360c..8d3deddea7 100644 --- a/examples/pubsub/chat/go.sum +++ b/examples/pubsub/chat/go.sum @@ -401,6 +401,7 @@ github.com/libp2p/go-addr-util v0.1.0/go.mod h1:6I3ZYuFr2O/9D+SoyM0zEw0EF3YkldtT github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= +github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= github.com/libp2p/go-conn-security-multistream v0.2.1 h1:ft6/POSK7F+vl/2qzegnHDaXFU0iWB4yVTYrioC6Zy0= github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= @@ -409,6 +410,7 @@ github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVh github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20210818120414-1f382a4aa43a/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-autonat v0.4.2 h1:YMp7StMi2dof+baaxkbxaizXjY1RPvU71CXfxExzcUU= github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= github.com/libp2p/go-libp2p-blankhost v0.2.0 h1:3EsGAi0CBGcZ33GwRuXEYJLLPoVWyXJ1bcJzAJjINkk= diff --git a/go.mod b/go.mod index e47ddf66b1..1440a611c0 100644 --- a/go.mod +++ b/go.mod @@ -12,14 +12,17 @@ require ( github.com/ipfs/go-datastore v0.4.6 github.com/ipfs/go-detect-race v0.0.1 github.com/ipfs/go-ipfs-util v0.0.2 + github.com/ipfs/go-log v1.0.5 github.com/ipfs/go-log/v2 v2.3.0 github.com/jbenet/go-cienv v0.1.0 github.com/jbenet/goprocess v0.1.4 github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/koron/go-ssdp v0.0.2 // indirect github.com/libp2p/go-addr-util v0.1.0 + github.com/libp2p/go-buffer-pool v0.0.2 github.com/libp2p/go-conn-security-multistream v0.2.1 github.com/libp2p/go-eventbus v0.2.1 + github.com/libp2p/go-libp2p-asn-util v0.0.0-20210818120414-1f382a4aa43a github.com/libp2p/go-libp2p-autonat v0.4.2 github.com/libp2p/go-libp2p-blankhost v0.2.0 github.com/libp2p/go-libp2p-circuit v0.4.0 @@ -45,6 +48,7 @@ require ( github.com/multiformats/go-multiaddr v0.4.0 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multistream v0.2.2 + github.com/multiformats/go-varint v0.0.6 github.com/prometheus/common v0.30.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 4451e23c78..fac8839fb4 100644 --- a/go.sum +++ b/go.sum @@ -418,6 +418,8 @@ github.com/libp2p/go-addr-util v0.1.0/go.mod h1:6I3ZYuFr2O/9D+SoyM0zEw0EF3YkldtT github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= +github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= +github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= github.com/libp2p/go-conn-security-multistream v0.2.1 h1:ft6/POSK7F+vl/2qzegnHDaXFU0iWB4yVTYrioC6Zy0= @@ -432,6 +434,8 @@ github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZk github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20210818120414-1f382a4aa43a h1:6yEuCOY31elgeJ2KA2JiREZjIznvH6lOWCdHRuhgEgc= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20210818120414-1f382a4aa43a/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= diff --git a/p2p/host/circuitv2/client/client.go b/p2p/host/circuitv2/client/client.go new file mode 100644 index 0000000000..60d7e758f4 --- /dev/null +++ b/p2p/host/circuitv2/client/client.go @@ -0,0 +1,66 @@ +package client + +import ( + "context" + "sync" + + "github.com/libp2p/go-libp2p/p2p/host/circuitv2/proto" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + + logging "github.com/ipfs/go-log" + tptu "github.com/libp2p/go-libp2p-transport-upgrader" +) + +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 + host host.Host + upgrader *tptu.Upgrader + + incoming chan accept + + mx sync.Mutex + activeDials map[peer.ID]*completion + hopCount map[peer.ID]int +} + +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(ctx context.Context, h host.Host, upgrader *tptu.Upgrader) (*Client, error) { + return &Client{ + ctx: ctx, + host: h, + upgrader: upgrader, + incoming: make(chan accept), + activeDials: make(map[peer.ID]*completion), + hopCount: make(map[peer.ID]int), + }, nil +} + +// Start registers the circuit (client) protocol stream handlers +func (c *Client) Start() { + c.host.SetStreamHandler(proto.ProtoIDv1, c.handleStreamV1) + c.host.SetStreamHandler(proto.ProtoIDv2Stop, c.handleStreamV2) +} diff --git a/p2p/host/circuitv2/client/conn.go b/p2p/host/circuitv2/client/conn.go new file mode 100644 index 0000000000..dc770768c3 --- /dev/null +++ b/p2p/host/circuitv2/client/conn.go @@ -0,0 +1,145 @@ +package client + +import ( + "fmt" + "net" + "time" + + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + + 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.Stat + + 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().Pretty(), + ) + 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().Pretty(), + Remote: c.remote.ID.Pretty(), + } +} + +// ConnStat interface +var _ network.ConnStat = (*Conn)(nil) + +func (c *Conn) Stat() network.Stat { + 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) + } +} diff --git a/p2p/host/circuitv2/client/dial.go b/p2p/host/circuitv2/client/dial.go new file mode 100644 index 0000000000..535521fdc2 --- /dev/null +++ b/p2p/host/circuitv2/client/dial.go @@ -0,0 +1,239 @@ +package client + +import ( + "context" + "fmt" + "time" + + pbv1 "github.com/libp2p/go-libp2p-circuit/pb" + pbv2 "github.com/libp2p/go-libp2p/p2p/host/circuitv2/pb" + "github.com/libp2p/go-libp2p/p2p/host/circuitv2/proto" + "github.com/libp2p/go-libp2p/p2p/host/circuitv2/util" + + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/peerstore" + + 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, proto.ProtoIDv1) + if err != nil { + return nil, fmt.Errorf("error opening hop stream to relay: %w", err) + } + + switch s.Protocol() { + case proto.ProtoIDv2Hop: + return c.connectV2(s, dest) + + case proto.ProtoIDv1: + return c.connectV1(s, dest) + + default: + s.Reset() + return nil, fmt.Errorf("unexpected stream protocol: %s", s.Protocol()) + } +} + +func (c *Client) connectV2(s network.Stream, dest peer.AddrInfo) (*Conn, error) { + 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.Stat + 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 +} + +func (c *Client) connectV1(s network.Stream, dest peer.AddrInfo) (*Conn, error) { + rd := util.NewDelimitedReader(s, maxMessageSize) + wr := util.NewDelimitedWriter(s) + defer rd.Close() + + var msg pbv1.CircuitRelay + + msg.Type = pbv1.CircuitRelay_HOP.Enum() + msg.SrcPeer = util.PeerInfoToPeerV1(c.host.Peerstore().PeerInfo(c.host.ID())) + msg.DstPeer = util.PeerInfoToPeerV1(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() != pbv1.CircuitRelay_STATUS { + s.Reset() + return nil, newRelayError("unexpected relay response; not a status message (%d)", msg.GetType()) + } + + status := msg.GetCode() + if status != pbv1.CircuitRelay_SUCCESS { + s.Reset() + return nil, newRelayError("error opening relay circuit: %s (%d)", pbv1.CircuitRelay_Status_name[int32(status)], status) + } + + return &Conn{stream: s, remote: dest, client: c}, nil +} diff --git a/p2p/host/circuitv2/client/handlers.go b/p2p/host/circuitv2/client/handlers.go new file mode 100644 index 0000000000..78704846b2 --- /dev/null +++ b/p2p/host/circuitv2/client/handlers.go @@ -0,0 +1,172 @@ +package client + +import ( + "time" + + pbv1 "github.com/libp2p/go-libp2p-circuit/pb" + pbv2 "github.com/libp2p/go-libp2p/p2p/host/circuitv2/pb" + "github.com/libp2p/go-libp2p/p2p/host/circuitv2/util" + + "github.com/libp2p/go-libp2p-core/network" +) + +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.Stat + 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) + } +} + +func (c *Client) handleStreamV1(s network.Stream) { + log.Debugf("new relay/v1 stream from: %s", s.Conn().RemotePeer()) + + s.SetReadDeadline(time.Now().Add(StreamTimeout)) + + rd := util.NewDelimitedReader(s, maxMessageSize) + defer rd.Close() + + writeResponse := func(status pbv1.CircuitRelay_Status) error { + wr := util.NewDelimitedWriter(s) + + var msg pbv1.CircuitRelay + msg.Type = pbv1.CircuitRelay_STATUS.Enum() + msg.Code = status.Enum() + + return wr.WriteMsg(&msg) + } + + handleError := func(status pbv1.CircuitRelay_Status) { + log.Debugf("protocol error: %s (%d)", pbv1.CircuitRelay_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 pbv1.CircuitRelay + + err := rd.ReadMsg(&msg) + if err != nil { + handleError(pbv1.CircuitRelay_MALFORMED_MESSAGE) + return + } + // reset stream deadline as message has been read + s.SetReadDeadline(time.Time{}) + + switch msg.GetType() { + case pbv1.CircuitRelay_STOP: + + case pbv1.CircuitRelay_HOP: + handleError(pbv1.CircuitRelay_HOP_CANT_SPEAK_RELAY) + return + + case pbv1.CircuitRelay_CAN_HOP: + handleError(pbv1.CircuitRelay_HOP_CANT_SPEAK_RELAY) + return + + default: + log.Debugf("unexpected relay handshake: %d", msg.GetType()) + handleError(pbv1.CircuitRelay_MALFORMED_MESSAGE) + return + } + + src, err := util.PeerToPeerInfoV1(msg.GetSrcPeer()) + if err != nil { + handleError(pbv1.CircuitRelay_STOP_SRC_MULTIADDR_INVALID) + return + } + + dst, err := util.PeerToPeerInfoV1(msg.GetDstPeer()) + if err != nil || dst.ID != c.host.ID() { + handleError(pbv1.CircuitRelay_STOP_DST_MULTIADDR_INVALID) + return + } + + log.Debugf("incoming relay connection from: %s", src.ID) + + select { + case c.incoming <- accept{ + conn: &Conn{stream: s, remote: src, client: c}, + writeResponse: func() error { + return writeResponse(pbv1.CircuitRelay_SUCCESS) + }, + }: + case <-time.After(AcceptTimeout): + handleError(pbv1.CircuitRelay_STOP_RELAY_REFUSED) + } +} diff --git a/p2p/host/circuitv2/client/listen.go b/p2p/host/circuitv2/client/listen.go new file mode 100644 index 0000000000..fcfb9fa690 --- /dev/null +++ b/p2p/host/circuitv2/client/listen.go @@ -0,0 +1,54 @@ +package client + +import ( + "net" + + 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, l.ctx.Err() + } + } +} + +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 { + // noop for now + return nil +} diff --git a/p2p/host/circuitv2/client/reservation.go b/p2p/host/circuitv2/client/reservation.go new file mode 100644 index 0000000000..479a147631 --- /dev/null +++ b/p2p/host/circuitv2/client/reservation.go @@ -0,0 +1,122 @@ +package client + +import ( + "context" + "fmt" + "time" + + pbv2 "github.com/libp2p/go-libp2p/p2p/host/circuitv2/pb" + "github.com/libp2p/go-libp2p/p2p/host/circuitv2/proto" + "github.com/libp2p/go-libp2p/p2p/host/circuitv2/util" + + "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" + + 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 +} + +// 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, 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, fmt.Errorf("error writing reservation message: %w", err) + } + + msg.Reset() + + if err := rd.ReadMsg(&msg); err != nil { + s.Reset() + return nil, fmt.Errorf("error reading reservation response message: %w", err) + } + + if msg.GetType() != pbv2.HopMessage_STATUS { + return nil, fmt.Errorf("unexpected relay response: not a status message (%d)", msg.GetType()) + } + + if status := msg.GetStatus(); status != pbv2.Status_OK { + return nil, fmt.Errorf("reservation failed: %s (%d)", pbv2.Status_name[int32(status)], status) + } + + rsvp := msg.GetReservation() + if rsvp == nil { + return nil, fmt.Errorf("missing reservation info") + } + + result := &Reservation{} + result.Expiration = time.Unix(int64(rsvp.GetExpire()), 0) + + for _, ab := range rsvp.GetAddrs() { + 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, fmt.Errorf("error consuming voucher envelope: %w", err) + } + + voucher, ok := rec.(*proto.ReservationVoucher) + if !ok { + return nil, fmt.Errorf("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 +} diff --git a/p2p/host/circuitv2/client/transport.go b/p2p/host/circuitv2/client/transport.go new file mode 100644 index 0000000000..406188cf42 --- /dev/null +++ b/p2p/host/circuitv2/client/transport.go @@ -0,0 +1,80 @@ +package client + +import ( + "context" + "fmt" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/transport" + + tptu "github.com/libp2p/go-libp2p-transport-upgrader" + 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(ctx context.Context, h host.Host, upgrader *tptu.Upgrader) error { + n, ok := h.Network().(transport.TransportNetwork) + if !ok { + return fmt.Errorf("%v is not a transport network", h.Network()) + } + + c, err := New(ctx, 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) + +func (c *Client) Dial(ctx context.Context, a ma.Multiaddr, p peer.ID) (transport.CapableConn, error) { + conn, err := c.dial(ctx, a, p) + if err != nil { + return nil, err + } + + conn.tagHop() + + return c.upgrader.UpgradeOutbound(ctx, c, conn, p) +} + +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 +} diff --git a/p2p/host/circuitv2/pb/Makefile b/p2p/host/circuitv2/pb/Makefile new file mode 100644 index 0000000000..c360a6fb95 --- /dev/null +++ b/p2p/host/circuitv2/pb/Makefile @@ -0,0 +1,11 @@ +PB = $(wildcard *.proto) +GO = $(PB:.proto=.pb.go) + +all: $(GO) + +%.pb.go: %.proto + protoc --gogofast_out=. $< + +clean: + rm -f *.pb.go + rm -f *.go diff --git a/p2p/host/circuitv2/pb/circuit.pb.go b/p2p/host/circuitv2/pb/circuit.pb.go new file mode 100644 index 0000000000..f908d0d761 --- /dev/null +++ b/p2p/host/circuitv2/pb/circuit.pb.go @@ -0,0 +1,1766 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: circuit.proto + +package circuit_pb + +import ( + fmt "fmt" + github_com_gogo_protobuf_proto "github.com/gogo/protobuf/proto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type Status int32 + +const ( + 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 +) + +var Status_name = map[int32]string{ + 100: "OK", + 200: "RESERVATION_REFUSED", + 201: "RESOURCE_LIMIT_EXCEEDED", + 202: "PERMISSION_DENIED", + 203: "CONNECTION_FAILED", + 204: "NO_RESERVATION", + 400: "MALFORMED_MESSAGE", + 401: "UNEXPECTED_MESSAGE", +} + +var Status_value = map[string]int32{ + "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 proto.EnumName(Status_name, int32(x)) +} + +func (x *Status) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Status_value, data, "Status") + if err != nil { + return err + } + *x = Status(value) + return nil +} + +func (Status) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_ed01bbc211f15e47, []int{0} +} + +type HopMessage_Type int32 + +const ( + HopMessage_RESERVE HopMessage_Type = 0 + HopMessage_CONNECT HopMessage_Type = 1 + HopMessage_STATUS HopMessage_Type = 2 +) + +var HopMessage_Type_name = map[int32]string{ + 0: "RESERVE", + 1: "CONNECT", + 2: "STATUS", +} + +var 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 proto.EnumName(HopMessage_Type_name, int32(x)) +} + +func (x *HopMessage_Type) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(HopMessage_Type_value, data, "HopMessage_Type") + if err != nil { + return err + } + *x = HopMessage_Type(value) + return nil +} + +func (HopMessage_Type) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_ed01bbc211f15e47, []int{0, 0} +} + +type StopMessage_Type int32 + +const ( + StopMessage_CONNECT StopMessage_Type = 0 + StopMessage_STATUS StopMessage_Type = 1 +) + +var StopMessage_Type_name = map[int32]string{ + 0: "CONNECT", + 1: "STATUS", +} + +var 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 proto.EnumName(StopMessage_Type_name, int32(x)) +} + +func (x *StopMessage_Type) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(StopMessage_Type_value, data, "StopMessage_Type") + if err != nil { + return err + } + *x = StopMessage_Type(value) + return nil +} + +func (StopMessage_Type) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_ed01bbc211f15e47, []int{1, 0} +} + +type HopMessage struct { + Type *HopMessage_Type `protobuf:"varint,1,req,name=type,enum=circuit.pb.HopMessage_Type" json:"type,omitempty"` + Peer *Peer `protobuf:"bytes,2,opt,name=peer" json:"peer,omitempty"` + Reservation *Reservation `protobuf:"bytes,3,opt,name=reservation" json:"reservation,omitempty"` + Limit *Limit `protobuf:"bytes,4,opt,name=limit" json:"limit,omitempty"` + Status *Status `protobuf:"varint,5,opt,name=status,enum=circuit.pb.Status" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HopMessage) Reset() { *m = HopMessage{} } +func (m *HopMessage) String() string { return proto.CompactTextString(m) } +func (*HopMessage) ProtoMessage() {} +func (*HopMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_ed01bbc211f15e47, []int{0} +} +func (m *HopMessage) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *HopMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_HopMessage.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *HopMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_HopMessage.Merge(m, src) +} +func (m *HopMessage) XXX_Size() int { + return m.Size() +} +func (m *HopMessage) XXX_DiscardUnknown() { + xxx_messageInfo_HopMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_HopMessage proto.InternalMessageInfo + +func (m *HopMessage) GetType() HopMessage_Type { + if m != nil && m.Type != nil { + return *m.Type + } + return HopMessage_RESERVE +} + +func (m *HopMessage) GetPeer() *Peer { + if m != nil { + return m.Peer + } + return nil +} + +func (m *HopMessage) GetReservation() *Reservation { + if m != nil { + return m.Reservation + } + return nil +} + +func (m *HopMessage) GetLimit() *Limit { + if m != nil { + return m.Limit + } + return nil +} + +func (m *HopMessage) GetStatus() Status { + if m != nil && m.Status != nil { + return *m.Status + } + return Status_OK +} + +type StopMessage struct { + Type *StopMessage_Type `protobuf:"varint,1,req,name=type,enum=circuit.pb.StopMessage_Type" json:"type,omitempty"` + Peer *Peer `protobuf:"bytes,2,opt,name=peer" json:"peer,omitempty"` + Limit *Limit `protobuf:"bytes,3,opt,name=limit" json:"limit,omitempty"` + Status *Status `protobuf:"varint,4,opt,name=status,enum=circuit.pb.Status" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *StopMessage) Reset() { *m = StopMessage{} } +func (m *StopMessage) String() string { return proto.CompactTextString(m) } +func (*StopMessage) ProtoMessage() {} +func (*StopMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_ed01bbc211f15e47, []int{1} +} +func (m *StopMessage) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *StopMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_StopMessage.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *StopMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_StopMessage.Merge(m, src) +} +func (m *StopMessage) XXX_Size() int { + return m.Size() +} +func (m *StopMessage) XXX_DiscardUnknown() { + xxx_messageInfo_StopMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_StopMessage proto.InternalMessageInfo + +func (m *StopMessage) GetType() StopMessage_Type { + if m != nil && m.Type != nil { + return *m.Type + } + return StopMessage_CONNECT +} + +func (m *StopMessage) GetPeer() *Peer { + if m != nil { + return m.Peer + } + return nil +} + +func (m *StopMessage) GetLimit() *Limit { + if m != nil { + return m.Limit + } + return nil +} + +func (m *StopMessage) GetStatus() Status { + if m != nil && m.Status != nil { + return *m.Status + } + return Status_OK +} + +type Peer struct { + Id []byte `protobuf:"bytes,1,req,name=id" json:"id,omitempty"` + Addrs [][]byte `protobuf:"bytes,2,rep,name=addrs" json:"addrs,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Peer) Reset() { *m = Peer{} } +func (m *Peer) String() string { return proto.CompactTextString(m) } +func (*Peer) ProtoMessage() {} +func (*Peer) Descriptor() ([]byte, []int) { + return fileDescriptor_ed01bbc211f15e47, []int{2} +} +func (m *Peer) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Peer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Peer.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Peer) XXX_Merge(src proto.Message) { + xxx_messageInfo_Peer.Merge(m, src) +} +func (m *Peer) XXX_Size() int { + return m.Size() +} +func (m *Peer) XXX_DiscardUnknown() { + xxx_messageInfo_Peer.DiscardUnknown(m) +} + +var xxx_messageInfo_Peer proto.InternalMessageInfo + +func (m *Peer) GetId() []byte { + if m != nil { + return m.Id + } + return nil +} + +func (m *Peer) GetAddrs() [][]byte { + if m != nil { + return m.Addrs + } + return nil +} + +type Reservation struct { + Expire *uint64 `protobuf:"varint,1,opt,name=expire" json:"expire,omitempty"` + Addrs [][]byte `protobuf:"bytes,2,rep,name=addrs" json:"addrs,omitempty"` + Voucher []byte `protobuf:"bytes,3,opt,name=voucher" json:"voucher,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Reservation) Reset() { *m = Reservation{} } +func (m *Reservation) String() string { return proto.CompactTextString(m) } +func (*Reservation) ProtoMessage() {} +func (*Reservation) Descriptor() ([]byte, []int) { + return fileDescriptor_ed01bbc211f15e47, []int{3} +} +func (m *Reservation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Reservation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Reservation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Reservation) XXX_Merge(src proto.Message) { + xxx_messageInfo_Reservation.Merge(m, src) +} +func (m *Reservation) XXX_Size() int { + return m.Size() +} +func (m *Reservation) XXX_DiscardUnknown() { + xxx_messageInfo_Reservation.DiscardUnknown(m) +} + +var xxx_messageInfo_Reservation proto.InternalMessageInfo + +func (m *Reservation) GetExpire() uint64 { + if m != nil && m.Expire != nil { + return *m.Expire + } + return 0 +} + +func (m *Reservation) GetAddrs() [][]byte { + if m != nil { + return m.Addrs + } + return nil +} + +func (m *Reservation) GetVoucher() []byte { + if m != nil { + return m.Voucher + } + return nil +} + +type Limit struct { + Duration *uint32 `protobuf:"varint,1,opt,name=duration" json:"duration,omitempty"` + Data *uint64 `protobuf:"varint,2,opt,name=data" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Limit) Reset() { *m = Limit{} } +func (m *Limit) String() string { return proto.CompactTextString(m) } +func (*Limit) ProtoMessage() {} +func (*Limit) Descriptor() ([]byte, []int) { + return fileDescriptor_ed01bbc211f15e47, []int{4} +} +func (m *Limit) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Limit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Limit.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Limit) XXX_Merge(src proto.Message) { + xxx_messageInfo_Limit.Merge(m, src) +} +func (m *Limit) XXX_Size() int { + return m.Size() +} +func (m *Limit) XXX_DiscardUnknown() { + xxx_messageInfo_Limit.DiscardUnknown(m) +} + +var xxx_messageInfo_Limit proto.InternalMessageInfo + +func (m *Limit) GetDuration() uint32 { + if m != nil && m.Duration != nil { + return *m.Duration + } + return 0 +} + +func (m *Limit) GetData() uint64 { + if m != nil && m.Data != nil { + return *m.Data + } + return 0 +} + +func init() { + proto.RegisterEnum("circuit.pb.Status", Status_name, Status_value) + proto.RegisterEnum("circuit.pb.HopMessage_Type", HopMessage_Type_name, HopMessage_Type_value) + proto.RegisterEnum("circuit.pb.StopMessage_Type", StopMessage_Type_name, StopMessage_Type_value) + proto.RegisterType((*HopMessage)(nil), "circuit.pb.HopMessage") + proto.RegisterType((*StopMessage)(nil), "circuit.pb.StopMessage") + proto.RegisterType((*Peer)(nil), "circuit.pb.Peer") + proto.RegisterType((*Reservation)(nil), "circuit.pb.Reservation") + proto.RegisterType((*Limit)(nil), "circuit.pb.Limit") +} + +func init() { proto.RegisterFile("circuit.proto", fileDescriptor_ed01bbc211f15e47) } + +var fileDescriptor_ed01bbc211f15e47 = []byte{ + // 516 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x92, 0xcf, 0x8a, 0xd3, 0x50, + 0x14, 0xc6, 0xe7, 0xa6, 0x69, 0x47, 0x4e, 0x3b, 0xe5, 0xce, 0x19, 0x99, 0x09, 0x3a, 0x8c, 0xa5, + 0x08, 0x96, 0x41, 0xaa, 0x74, 0x23, 0x2e, 0x6b, 0x73, 0xaa, 0xc1, 0x26, 0x29, 0xf7, 0xa6, 0x32, + 0xbb, 0x12, 0x9b, 0x8b, 0x06, 0xd4, 0x86, 0x24, 0x1d, 0x9c, 0xb7, 0xd0, 0x47, 0xf0, 0x45, 0x5c, + 0x8f, 0x7f, 0x16, 0xee, 0xdd, 0x48, 0x9f, 0x44, 0x72, 0xdb, 0x69, 0x33, 0x20, 0x28, 0xb8, 0xcb, + 0x77, 0xbf, 0xef, 0xe4, 0xe6, 0xf7, 0x9d, 0xc0, 0xde, 0x2c, 0x4e, 0x67, 0x8b, 0x38, 0xef, 0x26, + 0xe9, 0x3c, 0x9f, 0x23, 0x6c, 0xe4, 0xcb, 0xf6, 0x27, 0x03, 0xe0, 0xd9, 0x3c, 0x71, 0x55, 0x96, + 0x85, 0xaf, 0x14, 0x3e, 0x00, 0x33, 0xbf, 0x48, 0x94, 0xc5, 0x5a, 0x46, 0xa7, 0xd9, 0xbb, 0xdd, + 0xdd, 0x26, 0xbb, 0xdb, 0x54, 0x37, 0xb8, 0x48, 0x94, 0xd0, 0x41, 0xbc, 0x0b, 0x66, 0xa2, 0x54, + 0x6a, 0x19, 0x2d, 0xd6, 0xa9, 0xf7, 0x78, 0x79, 0x60, 0xac, 0x54, 0x2a, 0xb4, 0x8b, 0x8f, 0xa1, + 0x9e, 0xaa, 0x4c, 0xa5, 0xe7, 0x61, 0x1e, 0xcf, 0xdf, 0x59, 0x15, 0x1d, 0x3e, 0x2a, 0x87, 0xc5, + 0xd6, 0x16, 0xe5, 0x2c, 0xde, 0x83, 0xea, 0x9b, 0xf8, 0x6d, 0x9c, 0x5b, 0xa6, 0x1e, 0xda, 0x2f, + 0x0f, 0x8d, 0x0a, 0x43, 0xac, 0x7c, 0x3c, 0x85, 0x5a, 0x96, 0x87, 0xf9, 0x22, 0xb3, 0xaa, 0x2d, + 0xd6, 0x69, 0xf6, 0xb0, 0x9c, 0x94, 0xda, 0x11, 0xeb, 0x44, 0xfb, 0x3e, 0x98, 0x05, 0x03, 0xd6, + 0x61, 0x57, 0x90, 0x24, 0xf1, 0x82, 0xf8, 0x4e, 0x21, 0x06, 0xbe, 0xe7, 0xd1, 0x20, 0xe0, 0x0c, + 0x01, 0x6a, 0x32, 0xe8, 0x07, 0x13, 0xc9, 0x8d, 0xf6, 0x4f, 0x06, 0x75, 0x99, 0x6f, 0x4b, 0x7a, + 0x78, 0xad, 0xa4, 0xe3, 0xeb, 0xf7, 0xfc, 0x47, 0x4b, 0x1b, 0xd4, 0xca, 0x3f, 0xa3, 0x9a, 0x7f, + 0x45, 0xbd, 0xb3, 0x45, 0xbd, 0xa2, 0xdb, 0x29, 0xd1, 0xb1, 0xa2, 0x8b, 0xe2, 0x1b, 0xb0, 0x09, + 0x46, 0x1c, 0x69, 0xa6, 0x86, 0x30, 0xe2, 0x08, 0x6f, 0x42, 0x35, 0x8c, 0xa2, 0x34, 0xb3, 0x8c, + 0x56, 0xa5, 0xd3, 0x10, 0x2b, 0xd1, 0x9e, 0x40, 0xbd, 0xb4, 0x2a, 0x3c, 0x84, 0x9a, 0x7a, 0x9f, + 0xc4, 0x69, 0x51, 0x06, 0xeb, 0x98, 0x62, 0xad, 0xfe, 0x3c, 0x8c, 0x16, 0xec, 0x9e, 0xcf, 0x17, + 0xb3, 0xd7, 0x2a, 0xd5, 0x88, 0x0d, 0x71, 0x25, 0xdb, 0x8f, 0xa0, 0xaa, 0x09, 0xf1, 0x16, 0xdc, + 0x88, 0x16, 0xe9, 0xea, 0x37, 0x29, 0x5e, 0xb9, 0x27, 0x36, 0x1a, 0x11, 0xcc, 0x28, 0xcc, 0x43, + 0xdd, 0xa2, 0x29, 0xf4, 0xf3, 0xe9, 0x67, 0x06, 0xb5, 0x15, 0x31, 0xd6, 0xc0, 0xf0, 0x9f, 0xf3, + 0x08, 0x2d, 0x38, 0x58, 0x2d, 0xb5, 0x1f, 0x38, 0xbe, 0x37, 0x15, 0x34, 0x9c, 0x48, 0xb2, 0xf9, + 0x25, 0xc3, 0x63, 0x38, 0x12, 0x24, 0xfd, 0x89, 0x18, 0xd0, 0x74, 0xe4, 0xb8, 0x4e, 0x30, 0xa5, + 0xb3, 0x01, 0x91, 0x4d, 0x36, 0xff, 0xc2, 0xf0, 0x10, 0xf6, 0xc7, 0x24, 0x5c, 0x47, 0xca, 0x62, + 0xcc, 0x26, 0xcf, 0x21, 0x9b, 0x7f, 0xd5, 0xe7, 0xeb, 0xe6, 0x8a, 0xf3, 0x61, 0xdf, 0x19, 0x91, + 0xcd, 0xbf, 0x31, 0x3c, 0x80, 0xa6, 0xe7, 0x4f, 0x4b, 0x57, 0xf1, 0xef, 0x3a, 0xec, 0xf6, 0x47, + 0x43, 0x5f, 0xb8, 0x64, 0x4f, 0x5d, 0x92, 0xb2, 0xff, 0x94, 0xf8, 0x87, 0x0a, 0x1e, 0x01, 0x4e, + 0x3c, 0x3a, 0x1b, 0xd3, 0x20, 0x28, 0x19, 0x1f, 0x2b, 0x4f, 0x1a, 0x97, 0xcb, 0x13, 0xf6, 0x63, + 0x79, 0xc2, 0x7e, 0x2d, 0x4f, 0xd8, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa7, 0x24, 0x77, 0x69, + 0xaa, 0x03, 0x00, 0x00, +} + +func (m *HopMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *HopMessage) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *HopMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Status != nil { + i = encodeVarintCircuit(dAtA, i, uint64(*m.Status)) + i-- + dAtA[i] = 0x28 + } + if m.Limit != nil { + { + size, err := m.Limit.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCircuit(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.Reservation != nil { + { + size, err := m.Reservation.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCircuit(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.Peer != nil { + { + size, err := m.Peer.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCircuit(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.Type == nil { + return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("type") + } else { + i = encodeVarintCircuit(dAtA, i, uint64(*m.Type)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *StopMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StopMessage) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StopMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Status != nil { + i = encodeVarintCircuit(dAtA, i, uint64(*m.Status)) + i-- + dAtA[i] = 0x20 + } + if m.Limit != nil { + { + size, err := m.Limit.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCircuit(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.Peer != nil { + { + size, err := m.Peer.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCircuit(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.Type == nil { + return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("type") + } else { + i = encodeVarintCircuit(dAtA, i, uint64(*m.Type)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Peer) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Peer) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Peer) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.Addrs) > 0 { + for iNdEx := len(m.Addrs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Addrs[iNdEx]) + copy(dAtA[i:], m.Addrs[iNdEx]) + i = encodeVarintCircuit(dAtA, i, uint64(len(m.Addrs[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.Id == nil { + return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("id") + } else { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = encodeVarintCircuit(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Reservation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Reservation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Reservation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Voucher != nil { + i -= len(m.Voucher) + copy(dAtA[i:], m.Voucher) + i = encodeVarintCircuit(dAtA, i, uint64(len(m.Voucher))) + i-- + dAtA[i] = 0x1a + } + if len(m.Addrs) > 0 { + for iNdEx := len(m.Addrs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Addrs[iNdEx]) + copy(dAtA[i:], m.Addrs[iNdEx]) + i = encodeVarintCircuit(dAtA, i, uint64(len(m.Addrs[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.Expire != nil { + i = encodeVarintCircuit(dAtA, i, uint64(*m.Expire)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Limit) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Limit) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Limit) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Data != nil { + i = encodeVarintCircuit(dAtA, i, uint64(*m.Data)) + i-- + dAtA[i] = 0x10 + } + if m.Duration != nil { + i = encodeVarintCircuit(dAtA, i, uint64(*m.Duration)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintCircuit(dAtA []byte, offset int, v uint64) int { + offset -= sovCircuit(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *HopMessage) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Type != nil { + n += 1 + sovCircuit(uint64(*m.Type)) + } + if m.Peer != nil { + l = m.Peer.Size() + n += 1 + l + sovCircuit(uint64(l)) + } + if m.Reservation != nil { + l = m.Reservation.Size() + n += 1 + l + sovCircuit(uint64(l)) + } + if m.Limit != nil { + l = m.Limit.Size() + n += 1 + l + sovCircuit(uint64(l)) + } + if m.Status != nil { + n += 1 + sovCircuit(uint64(*m.Status)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *StopMessage) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Type != nil { + n += 1 + sovCircuit(uint64(*m.Type)) + } + if m.Peer != nil { + l = m.Peer.Size() + n += 1 + l + sovCircuit(uint64(l)) + } + if m.Limit != nil { + l = m.Limit.Size() + n += 1 + l + sovCircuit(uint64(l)) + } + if m.Status != nil { + n += 1 + sovCircuit(uint64(*m.Status)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *Peer) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Id != nil { + l = len(m.Id) + n += 1 + l + sovCircuit(uint64(l)) + } + if len(m.Addrs) > 0 { + for _, b := range m.Addrs { + l = len(b) + n += 1 + l + sovCircuit(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *Reservation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Expire != nil { + n += 1 + sovCircuit(uint64(*m.Expire)) + } + if len(m.Addrs) > 0 { + for _, b := range m.Addrs { + l = len(b) + n += 1 + l + sovCircuit(uint64(l)) + } + } + if m.Voucher != nil { + l = len(m.Voucher) + n += 1 + l + sovCircuit(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *Limit) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Duration != nil { + n += 1 + sovCircuit(uint64(*m.Duration)) + } + if m.Data != nil { + n += 1 + sovCircuit(uint64(*m.Data)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func sovCircuit(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozCircuit(x uint64) (n int) { + return sovCircuit(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *HopMessage) Unmarshal(dAtA []byte) error { + var hasFields [1]uint64 + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: HopMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: HopMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + var v HopMessage_Type + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= HopMessage_Type(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Type = &v + hasFields[0] |= uint64(0x00000001) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Peer", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCircuit + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCircuit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Peer == nil { + m.Peer = &Peer{} + } + if err := m.Peer.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reservation", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCircuit + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCircuit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Reservation == nil { + m.Reservation = &Reservation{} + } + if err := m.Reservation.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Limit", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCircuit + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCircuit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Limit == nil { + m.Limit = &Limit{} + } + if err := m.Limit.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var v Status + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= Status(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Status = &v + default: + iNdEx = preIndex + skippy, err := skipCircuit(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCircuit + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthCircuit + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + if hasFields[0]&uint64(0x00000001) == 0 { + return github_com_gogo_protobuf_proto.NewRequiredNotSetError("type") + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StopMessage) Unmarshal(dAtA []byte) error { + var hasFields [1]uint64 + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StopMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StopMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + var v StopMessage_Type + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= StopMessage_Type(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Type = &v + hasFields[0] |= uint64(0x00000001) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Peer", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCircuit + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCircuit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Peer == nil { + m.Peer = &Peer{} + } + if err := m.Peer.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Limit", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCircuit + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCircuit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Limit == nil { + m.Limit = &Limit{} + } + if err := m.Limit.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var v Status + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= Status(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Status = &v + default: + iNdEx = preIndex + skippy, err := skipCircuit(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCircuit + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthCircuit + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + if hasFields[0]&uint64(0x00000001) == 0 { + return github_com_gogo_protobuf_proto.NewRequiredNotSetError("type") + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Peer) Unmarshal(dAtA []byte) error { + var hasFields [1]uint64 + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Peer: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Peer: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCircuit + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCircuit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = append(m.Id[:0], dAtA[iNdEx:postIndex]...) + if m.Id == nil { + m.Id = []byte{} + } + iNdEx = postIndex + hasFields[0] |= uint64(0x00000001) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Addrs", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCircuit + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCircuit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Addrs = append(m.Addrs, make([]byte, postIndex-iNdEx)) + copy(m.Addrs[len(m.Addrs)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCircuit(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCircuit + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthCircuit + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + if hasFields[0]&uint64(0x00000001) == 0 { + return github_com_gogo_protobuf_proto.NewRequiredNotSetError("id") + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Reservation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Reservation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Reservation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Expire", wireType) + } + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Expire = &v + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Addrs", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCircuit + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCircuit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Addrs = append(m.Addrs, make([]byte, postIndex-iNdEx)) + copy(m.Addrs[len(m.Addrs)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Voucher", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCircuit + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCircuit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Voucher = append(m.Voucher[:0], dAtA[iNdEx:postIndex]...) + if m.Voucher == nil { + m.Voucher = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCircuit(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCircuit + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthCircuit + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Limit) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Limit: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Limit: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Duration", wireType) + } + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Duration = &v + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCircuit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Data = &v + default: + iNdEx = preIndex + skippy, err := skipCircuit(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCircuit + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthCircuit + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipCircuit(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCircuit + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCircuit + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCircuit + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthCircuit + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupCircuit + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthCircuit + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthCircuit = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowCircuit = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupCircuit = fmt.Errorf("proto: unexpected end of group") +) diff --git a/p2p/host/circuitv2/pb/circuit.proto b/p2p/host/circuitv2/pb/circuit.proto new file mode 100644 index 0000000000..b73555cbee --- /dev/null +++ b/p2p/host/circuitv2/pb/circuit.proto @@ -0,0 +1,60 @@ +syntax = "proto2"; + +package circuit.pb; + +message HopMessage { + enum Type { + RESERVE = 0; + CONNECT = 1; + STATUS = 2; + } + + required 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; + } + + required Type type = 1; + + optional Peer peer = 2; + optional Limit limit = 3; + + optional Status status = 4; +} + +message Peer { + required bytes id = 1; + repeated bytes addrs = 2; +} + +message Reservation { + 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 { + OK = 100; + RESERVATION_REFUSED = 200; + RESOURCE_LIMIT_EXCEEDED = 201; + PERMISSION_DENIED = 202; + CONNECTION_FAILED = 203; + NO_RESERVATION = 204; + MALFORMED_MESSAGE = 400; + UNEXPECTED_MESSAGE = 401; +} diff --git a/p2p/host/circuitv2/pb/voucher.pb.go b/p2p/host/circuitv2/pb/voucher.pb.go new file mode 100644 index 0000000000..6fed0082e2 --- /dev/null +++ b/p2p/host/circuitv2/pb/voucher.pb.go @@ -0,0 +1,438 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: voucher.proto + +package circuit_pb + +import ( + fmt "fmt" + github_com_gogo_protobuf_proto "github.com/gogo/protobuf/proto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type ReservationVoucher struct { + Relay []byte `protobuf:"bytes,1,req,name=relay" json:"relay,omitempty"` + Peer []byte `protobuf:"bytes,2,req,name=peer" json:"peer,omitempty"` + Expiration *uint64 `protobuf:"varint,3,req,name=expiration" json:"expiration,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ReservationVoucher) Reset() { *m = ReservationVoucher{} } +func (m *ReservationVoucher) String() string { return proto.CompactTextString(m) } +func (*ReservationVoucher) ProtoMessage() {} +func (*ReservationVoucher) Descriptor() ([]byte, []int) { + return fileDescriptor_a22a9b0d3335ba25, []int{0} +} +func (m *ReservationVoucher) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ReservationVoucher) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ReservationVoucher.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ReservationVoucher) XXX_Merge(src proto.Message) { + xxx_messageInfo_ReservationVoucher.Merge(m, src) +} +func (m *ReservationVoucher) XXX_Size() int { + return m.Size() +} +func (m *ReservationVoucher) XXX_DiscardUnknown() { + xxx_messageInfo_ReservationVoucher.DiscardUnknown(m) +} + +var xxx_messageInfo_ReservationVoucher proto.InternalMessageInfo + +func (m *ReservationVoucher) GetRelay() []byte { + if m != nil { + return m.Relay + } + return nil +} + +func (m *ReservationVoucher) GetPeer() []byte { + if m != nil { + return m.Peer + } + return nil +} + +func (m *ReservationVoucher) GetExpiration() uint64 { + if m != nil && m.Expiration != nil { + return *m.Expiration + } + return 0 +} + +func init() { + proto.RegisterType((*ReservationVoucher)(nil), "circuit.pb.ReservationVoucher") +} + +func init() { proto.RegisterFile("voucher.proto", fileDescriptor_a22a9b0d3335ba25) } + +var fileDescriptor_a22a9b0d3335ba25 = []byte{ + // 135 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2d, 0xcb, 0x2f, 0x4d, + 0xce, 0x48, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x4a, 0xce, 0x2c, 0x4a, 0x2e, + 0xcd, 0x2c, 0xd1, 0x2b, 0x48, 0x52, 0x8a, 0xe3, 0x12, 0x0a, 0x4a, 0x2d, 0x4e, 0x2d, 0x2a, 0x4b, + 0x2c, 0xc9, 0xcc, 0xcf, 0x0b, 0x83, 0xa8, 0x13, 0x12, 0xe1, 0x62, 0x2d, 0x4a, 0xcd, 0x49, 0xac, + 0x94, 0x60, 0x54, 0x60, 0xd2, 0xe0, 0x09, 0x82, 0x70, 0x84, 0x84, 0xb8, 0x58, 0x0a, 0x52, 0x53, + 0x8b, 0x24, 0x98, 0xc0, 0x82, 0x60, 0xb6, 0x90, 0x1c, 0x17, 0x57, 0x6a, 0x45, 0x41, 0x66, 0x11, + 0x58, 0xbb, 0x04, 0xb3, 0x02, 0x93, 0x06, 0x4b, 0x10, 0x92, 0x88, 0x13, 0xcf, 0x89, 0x47, 0x72, + 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x08, 0x08, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0x81, 0x3a, 0xee, 0x89, 0x00, 0x00, 0x00, +} + +func (m *ReservationVoucher) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReservationVoucher) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ReservationVoucher) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Expiration == nil { + return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("expiration") + } else { + i = encodeVarintVoucher(dAtA, i, uint64(*m.Expiration)) + i-- + dAtA[i] = 0x18 + } + if m.Peer == nil { + return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("peer") + } else { + i -= len(m.Peer) + copy(dAtA[i:], m.Peer) + i = encodeVarintVoucher(dAtA, i, uint64(len(m.Peer))) + i-- + dAtA[i] = 0x12 + } + if m.Relay == nil { + return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("relay") + } else { + i -= len(m.Relay) + copy(dAtA[i:], m.Relay) + i = encodeVarintVoucher(dAtA, i, uint64(len(m.Relay))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintVoucher(dAtA []byte, offset int, v uint64) int { + offset -= sovVoucher(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ReservationVoucher) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Relay != nil { + l = len(m.Relay) + n += 1 + l + sovVoucher(uint64(l)) + } + if m.Peer != nil { + l = len(m.Peer) + n += 1 + l + sovVoucher(uint64(l)) + } + if m.Expiration != nil { + n += 1 + sovVoucher(uint64(*m.Expiration)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func sovVoucher(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozVoucher(x uint64) (n int) { + return sovVoucher(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ReservationVoucher) Unmarshal(dAtA []byte) error { + var hasFields [1]uint64 + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowVoucher + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ReservationVoucher: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReservationVoucher: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Relay", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowVoucher + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthVoucher + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthVoucher + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Relay = append(m.Relay[:0], dAtA[iNdEx:postIndex]...) + if m.Relay == nil { + m.Relay = []byte{} + } + iNdEx = postIndex + hasFields[0] |= uint64(0x00000001) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Peer", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowVoucher + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthVoucher + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthVoucher + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Peer = append(m.Peer[:0], dAtA[iNdEx:postIndex]...) + if m.Peer == nil { + m.Peer = []byte{} + } + iNdEx = postIndex + hasFields[0] |= uint64(0x00000002) + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Expiration", wireType) + } + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowVoucher + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Expiration = &v + hasFields[0] |= uint64(0x00000004) + default: + iNdEx = preIndex + skippy, err := skipVoucher(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthVoucher + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthVoucher + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + if hasFields[0]&uint64(0x00000001) == 0 { + return github_com_gogo_protobuf_proto.NewRequiredNotSetError("relay") + } + if hasFields[0]&uint64(0x00000002) == 0 { + return github_com_gogo_protobuf_proto.NewRequiredNotSetError("peer") + } + if hasFields[0]&uint64(0x00000004) == 0 { + return github_com_gogo_protobuf_proto.NewRequiredNotSetError("expiration") + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipVoucher(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowVoucher + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowVoucher + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowVoucher + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthVoucher + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupVoucher + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthVoucher + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthVoucher = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowVoucher = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupVoucher = fmt.Errorf("proto: unexpected end of group") +) diff --git a/p2p/host/circuitv2/pb/voucher.proto b/p2p/host/circuitv2/pb/voucher.proto new file mode 100644 index 0000000000..086440253b --- /dev/null +++ b/p2p/host/circuitv2/pb/voucher.proto @@ -0,0 +1,9 @@ +syntax = "proto2"; + +package circuit.pb; + +message ReservationVoucher { + required bytes relay = 1; + required bytes peer = 2; + required uint64 expiration = 3; +} diff --git a/p2p/host/circuitv2/proto/protocol.go b/p2p/host/circuitv2/proto/protocol.go new file mode 100644 index 0000000000..d27fc50986 --- /dev/null +++ b/p2p/host/circuitv2/proto/protocol.go @@ -0,0 +1,7 @@ +package proto + +const ( + ProtoIDv1 = "/libp2p/circuit/relay/0.1.0" + ProtoIDv2Hop = "/libp2p/circuit/relay/0.2.0/hop" + ProtoIDv2Stop = "/libp2p/circuit/relay/0.2.0/stop" +) diff --git a/p2p/host/circuitv2/proto/voucher.go b/p2p/host/circuitv2/proto/voucher.go new file mode 100644 index 0000000000..6b4c24c8ac --- /dev/null +++ b/p2p/host/circuitv2/proto/voucher.go @@ -0,0 +1,72 @@ +package proto + +import ( + "time" + + pbv2 "github.com/libp2p/go-libp2p/p2p/host/circuitv2/pb" + + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/record" +) + +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) { + relay := []byte(rv.Relay) + peer := []byte(rv.Peer) + expiration := uint64(rv.Expiration.Unix()) + pbrv := &pbv2.ReservationVoucher{ + Relay: relay, + Peer: peer, + Expiration: &expiration, + } + + return pbrv.Marshal() +} + +func (rv *ReservationVoucher) UnmarshalRecord(blob []byte) error { + pbrv := pbv2.ReservationVoucher{} + err := pbrv.Unmarshal(blob) + 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 +} diff --git a/p2p/host/circuitv2/proto/voucher_test.go b/p2p/host/circuitv2/proto/voucher_test.go new file mode 100644 index 0000000000..148bd77a9b --- /dev/null +++ b/p2p/host/circuitv2/proto/voucher_test.go @@ -0,0 +1,68 @@ +package proto + +import ( + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/record" +) + +func TestReservationVoucher(t *testing.T) { + relayPrivk, relayPubk, err := crypto.GenerateKeyPair(crypto.Ed25519, 0) + if err != nil { + t.Fatal(err) + } + + _, peerPubk, err := crypto.GenerateKeyPair(crypto.Ed25519, 0) + if err != nil { + t.Fatal(err) + } + + relayID, err := peer.IDFromPublicKey(relayPubk) + if err != nil { + t.Fatal(err) + } + + peerID, err := peer.IDFromPublicKey(peerPubk) + if err != nil { + t.Fatal(err) + } + + rsvp := &ReservationVoucher{ + Relay: relayID, + Peer: peerID, + Expiration: time.Now().Add(time.Hour), + } + + envelope, err := record.Seal(rsvp, relayPrivk) + if err != nil { + t.Fatal(err) + } + + blob, err := envelope.Marshal() + if err != nil { + t.Fatal(err) + } + + _, rec, err := record.ConsumeEnvelope(blob, RecordDomain) + if err != nil { + t.Fatal(err) + } + + rsvp2, ok := rec.(*ReservationVoucher) + if !ok { + t.Fatalf("invalid record type %+T", rec) + } + + if rsvp.Relay != rsvp2.Relay { + t.Fatal("relay IDs don't match") + } + if rsvp.Peer != rsvp2.Peer { + t.Fatal("peer IDs don't match") + } + if rsvp.Expiration.Unix() != rsvp2.Expiration.Unix() { + t.Fatal("expirations don't match") + } +} diff --git a/p2p/host/circuitv2/relay/acl.go b/p2p/host/circuitv2/relay/acl.go new file mode 100644 index 0000000000..0501051327 --- /dev/null +++ b/p2p/host/circuitv2/relay/acl.go @@ -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 +} diff --git a/p2p/host/circuitv2/relay/ipcs.go b/p2p/host/circuitv2/relay/ipcs.go new file mode 100644 index 0000000000..167e08e55b --- /dev/null +++ b/p2p/host/circuitv2/relay/ipcs.go @@ -0,0 +1,110 @@ +package relay + +import ( + "errors" + "net" + + "github.com/libp2p/go-libp2p-core/peer" + + asnutil "github.com/libp2p/go-libp2p-asn-util" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" +) + +var ( + ErrNoIP = errors.New("no IP address associated with peer") + ErrTooManyPeersInIP = errors.New("too many peers in IP address") + ErrTooManyPeersInASN = errors.New("too many peers in ASN") +) + +// IPConstraints implements reservation constraints per IP +type IPConstraints struct { + iplimit, asnlimit int + + peers map[peer.ID]net.IP + ips map[string]map[peer.ID]struct{} + asns map[string]map[peer.ID]struct{} +} + +// NewIPConstraints creates a new IPConstraints object. +// The methods are *not* thread-safe; an external lock must be held if synchronization +// is required. +func NewIPConstraints(rc Resources) *IPConstraints { + return &IPConstraints{ + iplimit: rc.MaxReservationsPerIP, + asnlimit: rc.MaxReservationsPerASN, + + peers: make(map[peer.ID]net.IP), + ips: make(map[string]map[peer.ID]struct{}), + asns: make(map[string]map[peer.ID]struct{}), + } +} + +// AddReservation adds a reservation for a given peer with a given multiaddr. +// If adding this reservation violates IP constraints, an error is returned. +func (ipcs *IPConstraints) AddReservation(p peer.ID, a ma.Multiaddr) error { + ip, err := manet.ToIP(a) + if err != nil { + return ErrNoIP + } + + ips := ip.String() + peersInIP := ipcs.ips[ips] + if len(peersInIP) >= ipcs.iplimit { + return ErrTooManyPeersInIP + } + + var peersInAsn map[peer.ID]struct{} + asn, _ := asnutil.Store.AsnForIPv6(ip) + peersInAsn = ipcs.asns[asn] + if len(peersInAsn) >= ipcs.asnlimit { + return ErrTooManyPeersInASN + } + + ipcs.peers[p] = ip + + if peersInIP == nil { + peersInIP = make(map[peer.ID]struct{}) + ipcs.ips[ips] = peersInIP + } + peersInIP[p] = struct{}{} + + if asn != "" { + if peersInAsn == nil { + peersInAsn = make(map[peer.ID]struct{}) + ipcs.asns[asn] = peersInAsn + } + peersInAsn[p] = struct{}{} + } + + return nil +} + +// RemoveReservation removes a peer from the constraints. +func (ipcs *IPConstraints) RemoveReservation(p peer.ID) { + ip, ok := ipcs.peers[p] + if !ok { + return + } + + ips := ip.String() + asn, _ := asnutil.Store.AsnForIPv6(ip) + + delete(ipcs.peers, p) + + peersInIP, ok := ipcs.ips[ips] + if ok { + delete(peersInIP, p) + if len(peersInIP) == 0 { + delete(ipcs.ips, ips) + } + } + + peersInAsn, ok := ipcs.asns[asn] + if ok { + delete(peersInAsn, p) + if len(peersInAsn) == 0 { + delete(ipcs.asns, asn) + } + } +} diff --git a/p2p/host/circuitv2/relay/options.go b/p2p/host/circuitv2/relay/options.go new file mode 100644 index 0000000000..3464115226 --- /dev/null +++ b/p2p/host/circuitv2/relay/options.go @@ -0,0 +1,27 @@ +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 + } +} + +// 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 + } +} diff --git a/p2p/host/circuitv2/relay/relay.go b/p2p/host/circuitv2/relay/relay.go new file mode 100644 index 0000000000..635c27601f --- /dev/null +++ b/p2p/host/circuitv2/relay/relay.go @@ -0,0 +1,522 @@ +package relay + +import ( + "context" + "fmt" + "io" + "sync" + "sync/atomic" + "time" + + pbv2 "github.com/libp2p/go-libp2p/p2p/host/circuitv2/pb" + "github.com/libp2p/go-libp2p/p2p/host/circuitv2/proto" + "github.com/libp2p/go-libp2p/p2p/host/circuitv2/util" + + "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" + + logging "github.com/ipfs/go-log" + pool "github.com/libp2p/go-buffer-pool" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" +) + +const ( + ReservationTagWeight = 10 + + StreamTimeout = time.Minute + ConnectTimeout = 30 * time.Second + HandshakeTimeout = time.Minute + + maxMessageSize = 4096 +) + +var log = logging.Logger("relay") + +// Relay is the (limited) relay service object. +type Relay struct { + closed uint32 + ctx context.Context + cancel func() + + host host.Host + rc Resources + acl ACLFilter + ipcs *IPConstraints + + mx sync.Mutex + rsvp map[peer.ID]time.Time + conns map[peer.ID]int + + selfAddr ma.Multiaddr +} + +// 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) + } + } + + r.ipcs = NewIPConstraints(r.rc) + r.selfAddr = ma.StringCast(fmt.Sprintf("/p2p/%s", h.ID())) + + h.SetStreamHandler(proto.ProtoIDv2Hop, r.handleStream) + h.Network().Notify( + &network.NotifyBundle{ + DisconnectedF: r.disconnected, + }) + go r.background() + + return r, nil +} + +func (r *Relay) Close() error { + if atomic.CompareAndSwapUint32(&r.closed, 0, 1) { + r.host.RemoveStreamHandler(proto.ProtoIDv2Hop) + r.cancel() + r.mx.Lock() + for p := range r.rsvp { + r.host.ConnManager().UntagPeer(p, "relay-reservation") + } + r.mx.Unlock() + } + return nil +} + +func (r *Relay) handleStream(s network.Stream) { + s.SetReadDeadline(time.Now().Add(StreamTimeout)) + + log.Infof("new relay stream from: %s", s.Conn().RemotePeer()) + + rd := util.NewDelimitedReader(s, maxMessageSize) + defer rd.Close() + + 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: + r.handleReserve(s, &msg) + + case pbv2.HopMessage_CONNECT: + r.handleConnect(s, &msg) + + default: + r.handleError(s, pbv2.Status_MALFORMED_MESSAGE) + } +} + +func (r *Relay) handleReserve(s network.Stream, msg *pbv2.HopMessage) { + defer s.Close() + + p := s.Conn().RemotePeer() + a := s.Conn().RemoteMultiaddr() + + if util.IsRelayAddr(a) { + log.Debugf("refusing relay reservation for %s; reservation attempt over relay connection") + r.handleError(s, pbv2.Status_PERMISSION_DENIED) + return + } + + 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 + } + + r.mx.Lock() + now := time.Now() + + _, exists := r.rsvp[p] + if !exists { + active := len(r.rsvp) + if active >= r.rc.MaxReservations { + r.mx.Unlock() + log.Debugf("refusing relay reservation for %s; too many reservations", p) + r.handleError(s, pbv2.Status_RESERVATION_REFUSED) + return + } + if err := r.ipcs.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 + } + } + + expire := now.Add(r.rc.ReservationTTL) + r.rsvp[p] = expire + r.host.ConnManager().TagPeer(p, "relay-reservation", ReservationTagWeight) + r.mx.Unlock() + + log.Debugf("reserving relay slot for %s", p) + + err := r.writeResponse(s, pbv2.Status_OK, r.makeReservationMsg(p, expire), r.makeLimitMsg(p)) + if err != nil { + s.Reset() + log.Debugf("error writing reservation response; retracting reservation for %s", p) + r.mx.Lock() + delete(r.rsvp, p) + r.ipcs.RemoveReservation(p) + r.host.ConnManager().UntagPeer(p, "relay-reservation") + r.mx.Unlock() + } +} + +func (r *Relay) handleConnect(s network.Stream, msg *pbv2.HopMessage) { + src := s.Conn().RemotePeer() + a := s.Conn().RemoteMultiaddr() + + if util.IsRelayAddr(a) { + log.Debugf("refusing connection from %s; connection attempt over relay connection") + r.handleError(s, pbv2.Status_PERMISSION_DENIED) + return + } + + dest, err := util.PeerToPeerInfoV2(msg.GetPeer()) + if err != nil { + r.handleError(s, pbv2.Status_MALFORMED_MESSAGE) + return + } + + 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) + r.handleError(s, pbv2.Status_PERMISSION_DENIED) + return + } + + 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) + r.handleError(s, pbv2.Status_NO_RESERVATION) + return + } + + 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) + r.handleError(s, pbv2.Status_RESOURCE_LIMIT_EXCEEDED) + return + } + r.conns[src]++ + + destConns := r.conns[dest.ID] + if destConns >= r.rc.MaxCircuits { + r.conns[src]-- + r.mx.Unlock() + log.Debugf("refusing connection from %s to %s; too many connecitons to %s", src, dest.ID, dest.ID) + r.handleError(s, pbv2.Status_RESOURCE_LIMIT_EXCEEDED) + return + } + r.conns[dest.ID]++ + r.mx.Unlock() + + cleanup := func() { + r.mx.Lock() + r.conns[src]-- + r.conns[dest.ID]-- + r.mx.Unlock() + } + + 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 + } + + // handshake + 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") + bs.Reset() + cleanup() + r.handleError(s, pbv2.Status_CONNECTION_FAILED) + return + } + + stopmsg.Reset() + + err = rd.ReadMsg(&stopmsg) + if err != nil { + log.Debugf("error reading stop response: %s", err.Error()) + bs.Reset() + cleanup() + r.handleError(s, pbv2.Status_CONNECTION_FAILED) + return + } + + if t := stopmsg.GetType(); t != pbv2.StopMessage_STATUS { + log.Debugf("unexpected stop response; not a status message (%d)", t) + bs.Reset() + cleanup() + r.handleError(s, pbv2.Status_CONNECTION_FAILED) + return + } + + if status := stopmsg.GetStatus(); status != pbv2.Status_OK { + log.Debugf("relay stop failure: %d", status) + bs.Reset() + cleanup() + r.handleError(s, pbv2.Status_CONNECTION_FAILED) + return + } + + 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 + } + + // reset deadline + bs.SetDeadline(time.Time{}) + + log.Infof("relaying connection from %s to %s", src, dest.ID) + + goroutines := new(int32) + *goroutines = 2 + + done := func() { + if atomic.AddInt32(goroutines, -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) + } +} + +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 := io.CopyBuffer(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 := io.CopyBuffer(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) +} + +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() { + 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() + + for p, expire := range r.rsvp { + if expire.Before(now) { + delete(r.rsvp, p) + r.ipcs.RemoveReservation(p) + r.host.ConnManager().UntagPeer(p, "relay-reservation") + } + } + + 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() + defer r.mx.Unlock() + + delete(r.rsvp, p) + r.ipcs.RemoveReservation(p) +} diff --git a/p2p/host/circuitv2/relay/resources.go b/p2p/host/circuitv2/relay/resources.go new file mode 100644 index 0000000000..345b527142 --- /dev/null +++ b/p2p/host/circuitv2/relay/resources.go @@ -0,0 +1,62 @@ +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 + + // MaxReservationsPerIP is the maximum number of reservations originating from the same + // IP address; default is 4. + 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, + + MaxReservationsPerIP: 4, + 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 + } +} diff --git a/p2p/host/circuitv2/test/compat_test.go b/p2p/host/circuitv2/test/compat_test.go new file mode 100644 index 0000000000..bcecba022f --- /dev/null +++ b/p2p/host/circuitv2/test/compat_test.go @@ -0,0 +1,177 @@ +package test + +import ( + "bytes" + "context" + "fmt" + "io" + "testing" + + v1 "github.com/libp2p/go-libp2p-circuit" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + + tptu "github.com/libp2p/go-libp2p-transport-upgrader" + ma "github.com/multiformats/go-multiaddr" +) + +func addTransportV1(t *testing.T, ctx context.Context, h host.Host, upgrader *tptu.Upgrader) { + err := v1.AddRelayTransport(ctx, h, upgrader) + if err != nil { + t.Fatal(err) + } +} + +func TestRelayCompatV2DialV1(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts, upgraders := getNetHosts(t, ctx, 3) + addTransportV1(t, ctx, hosts[0], upgraders[0]) + addTransport(t, ctx, hosts[2], upgraders[2]) + + rch := make(chan []byte, 1) + hosts[0].SetStreamHandler("test", func(s network.Stream) { + defer s.Close() + defer close(rch) + + buf := make([]byte, 1024) + nread := 0 + for nread < len(buf) { + n, err := s.Read(buf[nread:]) + nread += n + if err != nil { + if err == io.EOF { + break + } + t.Fatal(err) + } + } + + rch <- buf[:nread] + }) + + _, err := v1.NewRelay(ctx, hosts[1], upgraders[1], v1.OptHop) + if err != nil { + t.Fatal(err) + } + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + raddr, err := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s/p2p-circuit/p2p/%s", hosts[1].ID(), hosts[0].ID())) + if err != nil { + t.Fatal(err) + } + + err = hosts[2].Connect(ctx, peer.AddrInfo{ID: hosts[0].ID(), Addrs: []ma.Multiaddr{raddr}}) + if err != nil { + t.Fatal(err) + } + + conns := hosts[2].Network().ConnsToPeer(hosts[0].ID()) + if len(conns) != 1 { + t.Fatalf("expected 1 connection, but got %d", len(conns)) + } + if conns[0].Stat().Transient { + t.Fatal("expected non transient connection") + } + + s, err := hosts[2].NewStream(ctx, hosts[0].ID(), "test") + if err != nil { + t.Fatal(err) + } + + msg := []byte("relay works!") + nwritten, err := s.Write(msg) + if err != nil { + t.Fatal(err) + } + if nwritten != len(msg) { + t.Fatalf("expected to write %d bytes, but wrote %d instead", len(msg), nwritten) + } + s.CloseWrite() + + got := <-rch + if !bytes.Equal(msg, got) { + t.Fatalf("Wrong echo; expected %s but got %s", string(msg), string(got)) + } +} + +func TestRelayCompatV1DialV2(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts, upgraders := getNetHosts(t, ctx, 3) + addTransport(t, ctx, hosts[0], upgraders[0]) + addTransportV1(t, ctx, hosts[2], upgraders[2]) + + rch := make(chan []byte, 1) + hosts[0].SetStreamHandler("test", func(s network.Stream) { + defer s.Close() + defer close(rch) + + buf := make([]byte, 1024) + nread := 0 + for nread < len(buf) { + n, err := s.Read(buf[nread:]) + nread += n + if err != nil { + if err == io.EOF { + break + } + t.Fatal(err) + } + } + + rch <- buf[:nread] + }) + + _, err := v1.NewRelay(ctx, hosts[1], upgraders[1], v1.OptHop) + if err != nil { + t.Fatal(err) + } + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + raddr, err := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s/p2p-circuit/p2p/%s", hosts[1].ID(), hosts[0].ID())) + if err != nil { + t.Fatal(err) + } + + err = hosts[2].Connect(ctx, peer.AddrInfo{ID: hosts[0].ID(), Addrs: []ma.Multiaddr{raddr}}) + if err != nil { + t.Fatal(err) + } + + conns := hosts[2].Network().ConnsToPeer(hosts[0].ID()) + if len(conns) != 1 { + t.Fatalf("expected 1 connection, but got %d", len(conns)) + } + if conns[0].Stat().Transient { + t.Fatal("expected non transient connection") + } + + s, err := hosts[2].NewStream(ctx, hosts[0].ID(), "test") + if err != nil { + t.Fatal(err) + } + + msg := []byte("relay works!") + nwritten, err := s.Write(msg) + if err != nil { + t.Fatal(err) + } + if nwritten != len(msg) { + t.Fatalf("expected to write %d bytes, but wrote %d instead", len(msg), nwritten) + } + s.CloseWrite() + + got := <-rch + if !bytes.Equal(msg, got) { + t.Fatalf("Wrong echo; expected %s but got %s", string(msg), string(got)) + } +} diff --git a/p2p/host/circuitv2/test/e2e_test.go b/p2p/host/circuitv2/test/e2e_test.go new file mode 100644 index 0000000000..b822f913d1 --- /dev/null +++ b/p2p/host/circuitv2/test/e2e_test.go @@ -0,0 +1,361 @@ +package test + +import ( + "bytes" + "context" + "fmt" + "io" + "math/rand" + "testing" + "time" + + "github.com/libp2p/go-libp2p/p2p/host/circuitv2/client" + "github.com/libp2p/go-libp2p/p2p/host/circuitv2/relay" + + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/mux" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + + logging "github.com/ipfs/go-log" + bhost "github.com/libp2p/go-libp2p-blankhost" + metrics "github.com/libp2p/go-libp2p-core/metrics" + pstoremem "github.com/libp2p/go-libp2p-peerstore/pstoremem" + swarm "github.com/libp2p/go-libp2p-swarm" + swarmt "github.com/libp2p/go-libp2p-swarm/testing" + tptu "github.com/libp2p/go-libp2p-transport-upgrader" + tcp "github.com/libp2p/go-tcp-transport" + ma "github.com/multiformats/go-multiaddr" +) + +func init() { + // TODO temporary for debugging purposes; to be removed for merge. + logging.SetLogLevel("relay", "DEBUG") + logging.SetLogLevel("p2p-circuit", "DEBUG") +} + +func getNetHosts(t *testing.T, ctx context.Context, n int) (hosts []host.Host, upgraders []*tptu.Upgrader) { + for i := 0; i < n; i++ { + privk, pubk, err := crypto.GenerateKeyPair(crypto.Ed25519, 0) + if err != nil { + t.Fatal(err) + } + + p, err := peer.IDFromPublicKey(pubk) + if err != nil { + t.Fatal(err) + } + + ps := pstoremem.NewPeerstore() + err = ps.AddPrivKey(p, privk) + if err != nil { + t.Fatal(err) + } + + bwr := metrics.NewBandwidthCounter() + netw := swarm.NewSwarm(ctx, p, ps, bwr) + + upgrader := swarmt.GenUpgrader(netw) + upgraders = append(upgraders, upgrader) + + err = netw.AddTransport(tcp.NewTCPTransport(upgrader)) + if err != nil { + t.Fatal(err) + } + + err = netw.Listen(ma.StringCast("/ip4/127.0.0.1/tcp/0")) + if err != nil { + t.Fatal(err) + } + + h := bhost.NewBlankHost(netw) + + hosts = append(hosts, h) + } + + return hosts, upgraders +} + +func connect(t *testing.T, a, b host.Host) { + pi := peer.AddrInfo{ID: a.ID(), Addrs: a.Addrs()} + err := b.Connect(context.Background(), pi) + if err != nil { + t.Fatal(err) + } +} + +func addTransport(t *testing.T, ctx context.Context, h host.Host, upgrader *tptu.Upgrader) { + err := client.AddTransport(ctx, h, upgrader) + if err != nil { + t.Fatal(err) + } +} + +func TestBasicRelay(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts, upgraders := getNetHosts(t, ctx, 3) + addTransport(t, ctx, hosts[0], upgraders[0]) + addTransport(t, ctx, hosts[2], upgraders[2]) + + rch := make(chan []byte, 1) + hosts[0].SetStreamHandler("test", func(s network.Stream) { + defer s.Close() + defer close(rch) + + buf := make([]byte, 1024) + nread := 0 + for nread < len(buf) { + n, err := s.Read(buf[nread:]) + nread += n + if err != nil { + if err == io.EOF { + break + } + t.Fatal(err) + } + } + + rch <- buf[:nread] + }) + + r, err := relay.New(hosts[1]) + if err != nil { + t.Fatal(err) + } + defer r.Close() + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + rinfo := hosts[1].Peerstore().PeerInfo(hosts[1].ID()) + rsvp, err := client.Reserve(ctx, hosts[0], rinfo) + if err != nil { + t.Fatal(err) + } + + if rsvp.Voucher == nil { + t.Fatal("no reservation voucher") + } + + raddr, err := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s/p2p-circuit/p2p/%s", hosts[1].ID(), hosts[0].ID())) + if err != nil { + t.Fatal(err) + } + + err = hosts[2].Connect(ctx, peer.AddrInfo{ID: hosts[0].ID(), Addrs: []ma.Multiaddr{raddr}}) + if err != nil { + t.Fatal(err) + } + + conns := hosts[2].Network().ConnsToPeer(hosts[0].ID()) + if len(conns) != 1 { + t.Fatalf("expected 1 connection, but got %d", len(conns)) + } + if !conns[0].Stat().Transient { + t.Fatal("expected transient connection") + } + + s, err := hosts[2].NewStream(network.WithUseTransient(ctx, "test"), hosts[0].ID(), "test") + if err != nil { + t.Fatal(err) + } + + msg := []byte("relay works!") + nwritten, err := s.Write(msg) + if err != nil { + t.Fatal(err) + } + if nwritten != len(msg) { + t.Fatalf("expected to write %d bytes, but wrote %d instead", len(msg), nwritten) + } + s.CloseWrite() + + got := <-rch + if !bytes.Equal(msg, got) { + t.Fatalf("Wrong echo; expected %s but got %s", string(msg), string(got)) + } +} + +func TestRelayLimitTime(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts, upgraders := getNetHosts(t, ctx, 3) + addTransport(t, ctx, hosts[0], upgraders[0]) + addTransport(t, ctx, hosts[2], upgraders[2]) + + rch := make(chan error, 1) + hosts[0].SetStreamHandler("test", func(s network.Stream) { + defer s.Close() + defer close(rch) + + buf := make([]byte, 1024) + _, err := s.Read(buf) + rch <- err + }) + + rc := relay.DefaultResources() + rc.Limit.Duration = time.Second + + r, err := relay.New(hosts[1], relay.WithResources(rc)) + if err != nil { + t.Fatal(err) + } + defer r.Close() + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + rinfo := hosts[1].Peerstore().PeerInfo(hosts[1].ID()) + _, err = client.Reserve(ctx, hosts[0], rinfo) + if err != nil { + t.Fatal(err) + } + + raddr, err := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s/p2p-circuit/p2p/%s", hosts[1].ID(), hosts[0].ID())) + if err != nil { + t.Fatal(err) + } + + err = hosts[2].Connect(ctx, peer.AddrInfo{ID: hosts[0].ID(), Addrs: []ma.Multiaddr{raddr}}) + if err != nil { + t.Fatal(err) + } + + conns := hosts[2].Network().ConnsToPeer(hosts[0].ID()) + if len(conns) != 1 { + t.Fatalf("expected 1 connection, but got %d", len(conns)) + } + if !conns[0].Stat().Transient { + t.Fatal("expected transient connection") + } + + s, err := hosts[2].NewStream(network.WithUseTransient(ctx, "test"), hosts[0].ID(), "test") + if err != nil { + t.Fatal(err) + } + + time.Sleep(2 * time.Second) + n, err := s.Write([]byte("should be closed")) + if n > 0 { + t.Fatalf("expected to write 0 bytes, wrote %d", n) + } + if err != mux.ErrReset { + t.Fatalf("expected reset, but got %s", err) + } + + err = <-rch + if err != mux.ErrReset { + t.Fatalf("expected reset, but got %s", err) + } +} + +func TestRelayLimitData(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts, upgraders := getNetHosts(t, ctx, 3) + addTransport(t, ctx, hosts[0], upgraders[0]) + addTransport(t, ctx, hosts[2], upgraders[2]) + + rch := make(chan int, 1) + hosts[0].SetStreamHandler("test", func(s network.Stream) { + defer s.Close() + defer close(rch) + + buf := make([]byte, 1024) + for i := 0; i < 3; i++ { + n, err := s.Read(buf) + if err != nil { + t.Fatal(err) + } + rch <- n + } + + n, err := s.Read(buf) + if err != mux.ErrReset { + t.Fatalf("expected reset but got %s", err) + } + rch <- n + }) + + rc := relay.DefaultResources() + rc.Limit.Duration = time.Second + rc.Limit.Data = 4096 + + r, err := relay.New(hosts[1], relay.WithResources(rc)) + if err != nil { + t.Fatal(err) + } + defer r.Close() + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + rinfo := hosts[1].Peerstore().PeerInfo(hosts[1].ID()) + _, err = client.Reserve(ctx, hosts[0], rinfo) + if err != nil { + t.Fatal(err) + } + + raddr, err := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s/p2p-circuit/p2p/%s", hosts[1].ID(), hosts[0].ID())) + if err != nil { + t.Fatal(err) + } + + err = hosts[2].Connect(ctx, peer.AddrInfo{ID: hosts[0].ID(), Addrs: []ma.Multiaddr{raddr}}) + if err != nil { + t.Fatal(err) + } + + conns := hosts[2].Network().ConnsToPeer(hosts[0].ID()) + if len(conns) != 1 { + t.Fatalf("expected 1 connection, but got %d", len(conns)) + } + if !conns[0].Stat().Transient { + t.Fatal("expected transient connection") + } + + s, err := hosts[2].NewStream(network.WithUseTransient(ctx, "test"), hosts[0].ID(), "test") + if err != nil { + t.Fatal(err) + } + + buf := make([]byte, 1024) + for i := 0; i < 3; i++ { + _, err = rand.Read(buf) + if err != nil { + t.Fatal(err) + } + + n, err := s.Write(buf) + if err != nil { + t.Fatal(err) + } + if n != len(buf) { + t.Fatalf("expected to write %d bytes but wrote %d", len(buf), n) + } + + n = <-rch + if n != len(buf) { + t.Fatalf("expected to read %d bytes but read %d", len(buf), n) + } + } + + buf = make([]byte, 4096) + _, err = rand.Read(buf) + if err != nil { + t.Fatal(err) + } + + s.Write(buf) + + n := <-rch + if n != 0 { + t.Fatalf("expected to read 0 bytes but read %d", n) + } + +} diff --git a/p2p/host/circuitv2/test/empty.go b/p2p/host/circuitv2/test/empty.go new file mode 100644 index 0000000000..56e5404079 --- /dev/null +++ b/p2p/host/circuitv2/test/empty.go @@ -0,0 +1 @@ +package test diff --git a/p2p/host/circuitv2/test/ipcs_test.go b/p2p/host/circuitv2/test/ipcs_test.go new file mode 100644 index 0000000000..db96e09143 --- /dev/null +++ b/p2p/host/circuitv2/test/ipcs_test.go @@ -0,0 +1,69 @@ +package test + +import ( + "fmt" + "net" + "testing" + + "github.com/libp2p/go-libp2p/p2p/host/circuitv2/relay" + + "github.com/libp2p/go-libp2p-core/peer" + + ma "github.com/multiformats/go-multiaddr" +) + +func TestIPConstraints(t *testing.T) { + ipcs := relay.NewIPConstraints(relay.Resources{ + MaxReservationsPerIP: 1, + MaxReservationsPerASN: 2, + }) + + peerA := peer.ID("A") + peerB := peer.ID("B") + peerC := peer.ID("C") + peerD := peer.ID("D") + peerE := peer.ID("E") + + ipA := net.ParseIP("1.2.3.4") + ipB := ipA + ipC := net.ParseIP("2001:200::1") + ipD := net.ParseIP("2001:200::2") + ipE := net.ParseIP("2001:200::3") + + err := ipcs.AddReservation(peerA, ma.StringCast(fmt.Sprintf("/ip4/%s/tcp/1234", ipA))) + if err != nil { + t.Fatal(err) + } + + err = ipcs.AddReservation(peerB, ma.StringCast(fmt.Sprintf("/ip4/%s/tcp/1234", ipB))) + if err != relay.ErrTooManyPeersInIP { + t.Fatalf("unexpected error: %s", err) + } + + ipcs.RemoveReservation(peerA) + err = ipcs.AddReservation(peerB, ma.StringCast(fmt.Sprintf("/ip4/%s/tcp/1234", ipB))) + if err != nil { + t.Fatal(err) + } + + err = ipcs.AddReservation(peerC, ma.StringCast(fmt.Sprintf("/ip6/%s/tcp/1234", ipC))) + if err != nil { + t.Fatal(err) + } + + err = ipcs.AddReservation(peerD, ma.StringCast(fmt.Sprintf("/ip6/%s/tcp/1234", ipD))) + if err != nil { + t.Fatal(err) + } + + err = ipcs.AddReservation(peerE, ma.StringCast(fmt.Sprintf("/ip6/%s/tcp/1234", ipE))) + if err != relay.ErrTooManyPeersInASN { + t.Fatalf("unexpected error: %s", err) + } + + ipcs.RemoveReservation(peerD) + err = ipcs.AddReservation(peerE, ma.StringCast(fmt.Sprintf("/ip6/%s/tcp/1234", ipE))) + if err != nil { + t.Fatal(err) + } +} diff --git a/p2p/host/circuitv2/util/io.go b/p2p/host/circuitv2/util/io.go new file mode 100644 index 0000000000..911bad19f6 --- /dev/null +++ b/p2p/host/circuitv2/util/io.go @@ -0,0 +1,67 @@ +package util + +import ( + "errors" + "io" + + pool "github.com/libp2p/go-buffer-pool" + "github.com/libp2p/go-msgio/protoio" + + "github.com/gogo/protobuf/proto" + "github.com/multiformats/go-varint" +) + +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) protoio.WriteCloser { + return protoio.NewDelimitedWriter(w) +} diff --git a/p2p/host/circuitv2/util/ma.go b/p2p/host/circuitv2/util/ma.go new file mode 100644 index 0000000000..0dd820e20b --- /dev/null +++ b/p2p/host/circuitv2/util/ma.go @@ -0,0 +1,10 @@ +package util + +import ( + ma "github.com/multiformats/go-multiaddr" +) + +func IsRelayAddr(a ma.Multiaddr) bool { + _, err := a.ValueForProtocol(ma.P_CIRCUIT) + return err == nil +} diff --git a/p2p/host/circuitv2/util/pbconv.go b/p2p/host/circuitv2/util/pbconv.go new file mode 100644 index 0000000000..63074acc1a --- /dev/null +++ b/p2p/host/circuitv2/util/pbconv.go @@ -0,0 +1,97 @@ +package util + +import ( + "errors" + + pbv1 "github.com/libp2p/go-libp2p-circuit/pb" + pbv2 "github.com/libp2p/go-libp2p/p2p/host/circuitv2/pb" + + "github.com/libp2p/go-libp2p-core/peer" + + ma "github.com/multiformats/go-multiaddr" +) + +func PeerToPeerInfoV1(p *pbv1.CircuitRelay_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 + } + + var addrs []ma.Multiaddr + if len(p.Addrs) > 0 { + 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 PeerInfoToPeerV1(pi peer.AddrInfo) *pbv1.CircuitRelay_Peer { + var addrs [][]byte + if len(pi.Addrs) > 0 { + addrs = make([][]byte, 0, len(pi.Addrs)) + } + + for _, addr := range pi.Addrs { + addrs = append(addrs, addr.Bytes()) + } + + p := new(pbv1.CircuitRelay_Peer) + p.Id = []byte(pi.ID) + p.Addrs = addrs + + return p +} + +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 + } + + var addrs []ma.Multiaddr + if len(p.Addrs) > 0 { + 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 { + var addrs [][]byte + + if len(pi.Addrs) > 0 { + addrs = make([][]byte, 0, len(pi.Addrs)) + } + + for _, addr := range pi.Addrs { + addrs = append(addrs, addr.Bytes()) + } + + p := new(pbv2.Peer) + p.Id = []byte(pi.ID) + p.Addrs = addrs + + return p +} From b44b6de1b87d8e92c5daba69c14e56a0694a3045 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 6 Jul 2021 11:51:21 -0700 Subject: [PATCH 2/4] remove unneeded handleReserve function parameter --- p2p/host/circuitv2/relay/relay.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/host/circuitv2/relay/relay.go b/p2p/host/circuitv2/relay/relay.go index 635c27601f..85e95b6b9b 100644 --- a/p2p/host/circuitv2/relay/relay.go +++ b/p2p/host/circuitv2/relay/relay.go @@ -120,7 +120,7 @@ func (r *Relay) handleStream(s network.Stream) { switch msg.GetType() { case pbv2.HopMessage_RESERVE: - r.handleReserve(s, &msg) + r.handleReserve(s) case pbv2.HopMessage_CONNECT: r.handleConnect(s, &msg) @@ -130,7 +130,7 @@ func (r *Relay) handleStream(s network.Stream) { } } -func (r *Relay) handleReserve(s network.Stream, msg *pbv2.HopMessage) { +func (r *Relay) handleReserve(s network.Stream) { defer s.Close() p := s.Conn().RemotePeer() From 7a72c33aef2a3ef7d346867fa629d173f5751ae2 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 6 Jul 2021 12:34:47 -0700 Subject: [PATCH 3/4] remove rollback logic when writing reservation fails --- p2p/host/circuitv2/relay/relay.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/p2p/host/circuitv2/relay/relay.go b/p2p/host/circuitv2/relay/relay.go index 85e95b6b9b..d03aba9d7d 100644 --- a/p2p/host/circuitv2/relay/relay.go +++ b/p2p/host/circuitv2/relay/relay.go @@ -175,15 +175,12 @@ func (r *Relay) handleReserve(s network.Stream) { log.Debugf("reserving relay slot for %s", p) - err := r.writeResponse(s, pbv2.Status_OK, r.makeReservationMsg(p, expire), r.makeLimitMsg(p)) - if err != nil { - s.Reset() + // 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) - r.mx.Lock() - delete(r.rsvp, p) - r.ipcs.RemoveReservation(p) - r.host.ConnManager().UntagPeer(p, "relay-reservation") - r.mx.Unlock() + s.Reset() } } From c67489ddf292c548494af99f4ad196645d30b49c Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 5 Aug 2021 19:13:49 +0200 Subject: [PATCH 4/4] base limits on reservations issued (#132) * base limits on reservations issued * switch default reservation limits per peer and per IP * don't export the constructor for relay.constraints * panic when reading from crypto/rand fails * optimize IP-based reservation lookup * use lists instead of maps to save reservations * save expiry timestamp in reservations * use slices instead of linked lists for reservations * remove unused rand in constraints --- p2p/host/circuitv2/relay/constraints.go | 124 ++++++++++++++++ p2p/host/circuitv2/relay/constraints_test.go | 142 +++++++++++++++++++ p2p/host/circuitv2/relay/ipcs.go | 110 -------------- p2p/host/circuitv2/relay/relay.go | 21 +-- p2p/host/circuitv2/relay/resources.go | 10 +- p2p/host/circuitv2/test/ipcs_test.go | 69 --------- 6 files changed, 279 insertions(+), 197 deletions(-) create mode 100644 p2p/host/circuitv2/relay/constraints.go create mode 100644 p2p/host/circuitv2/relay/constraints_test.go delete mode 100644 p2p/host/circuitv2/relay/ipcs.go delete mode 100644 p2p/host/circuitv2/test/ipcs_test.go diff --git a/p2p/host/circuitv2/relay/constraints.go b/p2p/host/circuitv2/relay/constraints.go new file mode 100644 index 0000000000..1b0087ec89 --- /dev/null +++ b/p2p/host/circuitv2/relay/constraints.go @@ -0,0 +1,124 @@ +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 + if ip.To4() == nil { + 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) + } +} diff --git a/p2p/host/circuitv2/relay/constraints_test.go b/p2p/host/circuitv2/relay/constraints_test.go new file mode 100644 index 0000000000..0eaaa680d1 --- /dev/null +++ b/p2p/host/circuitv2/relay/constraints_test.go @@ -0,0 +1,142 @@ +package relay + +import ( + "crypto/rand" + "fmt" + "math" + "net" + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/test" + ma "github.com/multiformats/go-multiaddr" +) + +func randomIPv4Addr(t *testing.T) ma.Multiaddr { + t.Helper() + b := make([]byte, 4) + rand.Read(b) + addr, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/1234", net.IP(b))) + if err != nil { + t.Fatal(err) + } + return addr +} + +func TestConstraints(t *testing.T) { + infResources := func() *Resources { + return &Resources{ + MaxReservations: math.MaxInt32, + MaxReservationsPerPeer: math.MaxInt32, + MaxReservationsPerIP: math.MaxInt32, + MaxReservationsPerASN: math.MaxInt32, + } + } + const limit = 7 + + t.Run("total reservations", func(t *testing.T) { + res := infResources() + res.MaxReservations = limit + c := newConstraints(res) + for i := 0; i < limit; i++ { + if err := c.AddReservation(test.RandPeerIDFatal(t), randomIPv4Addr(t)); err != nil { + t.Fatal(err) + } + } + if err := c.AddReservation(test.RandPeerIDFatal(t), randomIPv4Addr(t)); err != errTooManyReservations { + t.Fatalf("expected to run into total reservation limit, got %v", err) + } + }) + + t.Run("reservations per peer", func(t *testing.T) { + p := test.RandPeerIDFatal(t) + res := infResources() + res.MaxReservationsPerPeer = limit + c := newConstraints(res) + for i := 0; i < limit; i++ { + if err := c.AddReservation(p, randomIPv4Addr(t)); err != nil { + t.Fatal(err) + } + } + if err := c.AddReservation(p, randomIPv4Addr(t)); err != errTooManyReservationsForPeer { + t.Fatalf("expected to run into total reservation limit, got %v", err) + } + if err := c.AddReservation(test.RandPeerIDFatal(t), randomIPv4Addr(t)); err != nil { + t.Fatalf("expected reservation for different peer to be possible, got %v", err) + } + }) + + t.Run("reservations per IP", func(t *testing.T) { + ip := randomIPv4Addr(t) + res := infResources() + res.MaxReservationsPerIP = limit + c := newConstraints(res) + for i := 0; i < limit; i++ { + if err := c.AddReservation(test.RandPeerIDFatal(t), ip); err != nil { + t.Fatal(err) + } + } + if err := c.AddReservation(test.RandPeerIDFatal(t), ip); err != errTooManyReservationsForIP { + t.Fatalf("expected to run into total reservation limit, got %v", err) + } + if err := c.AddReservation(test.RandPeerIDFatal(t), randomIPv4Addr(t)); err != nil { + t.Fatalf("expected reservation for different IP to be possible, got %v", err) + } + }) + + t.Run("reservations per ASN", func(t *testing.T) { + getAddr := func(t *testing.T, ip net.IP) ma.Multiaddr { + t.Helper() + addr, err := ma.NewMultiaddr(fmt.Sprintf("/ip6/%s/tcp/1234", ip)) + if err != nil { + t.Fatal(err) + } + return addr + } + + res := infResources() + res.MaxReservationsPerASN = limit + c := newConstraints(res) + const ipv6Prefix = "2a03:2880:f003:c07:face:b00c::" + for i := 0; i < limit; i++ { + addr := getAddr(t, net.ParseIP(fmt.Sprintf("%s%d", ipv6Prefix, i+1))) + if err := c.AddReservation(test.RandPeerIDFatal(t), addr); err != nil { + t.Fatal(err) + } + } + if err := c.AddReservation(test.RandPeerIDFatal(t), getAddr(t, net.ParseIP(fmt.Sprintf("%s%d", ipv6Prefix, 42)))); err != errTooManyReservationsForASN { + t.Fatalf("expected to run into total reservation limit, got %v", err) + } + if err := c.AddReservation(test.RandPeerIDFatal(t), randomIPv4Addr(t)); err != nil { + t.Fatalf("expected reservation for different IP to be possible, got %v", err) + } + }) +} + +func TestConstraintsCleanup(t *testing.T) { + origValidity := validity + defer func() { validity = origValidity }() + validity = 500 * time.Millisecond + + const limit = 7 + res := &Resources{ + MaxReservations: limit, + MaxReservationsPerPeer: math.MaxInt32, + MaxReservationsPerIP: math.MaxInt32, + MaxReservationsPerASN: math.MaxInt32, + } + c := newConstraints(res) + for i := 0; i < limit; i++ { + if err := c.AddReservation(test.RandPeerIDFatal(t), randomIPv4Addr(t)); err != nil { + t.Fatal(err) + } + } + if err := c.AddReservation(test.RandPeerIDFatal(t), randomIPv4Addr(t)); err != errTooManyReservations { + t.Fatalf("expected to run into total reservation limit, got %v", err) + } + + time.Sleep(validity + time.Millisecond) + if err := c.AddReservation(test.RandPeerIDFatal(t), randomIPv4Addr(t)); err != nil { + t.Fatalf("expected old reservations to have been garbage collected, %v", err) + } +} diff --git a/p2p/host/circuitv2/relay/ipcs.go b/p2p/host/circuitv2/relay/ipcs.go deleted file mode 100644 index 167e08e55b..0000000000 --- a/p2p/host/circuitv2/relay/ipcs.go +++ /dev/null @@ -1,110 +0,0 @@ -package relay - -import ( - "errors" - "net" - - "github.com/libp2p/go-libp2p-core/peer" - - asnutil "github.com/libp2p/go-libp2p-asn-util" - ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" -) - -var ( - ErrNoIP = errors.New("no IP address associated with peer") - ErrTooManyPeersInIP = errors.New("too many peers in IP address") - ErrTooManyPeersInASN = errors.New("too many peers in ASN") -) - -// IPConstraints implements reservation constraints per IP -type IPConstraints struct { - iplimit, asnlimit int - - peers map[peer.ID]net.IP - ips map[string]map[peer.ID]struct{} - asns map[string]map[peer.ID]struct{} -} - -// NewIPConstraints creates a new IPConstraints object. -// The methods are *not* thread-safe; an external lock must be held if synchronization -// is required. -func NewIPConstraints(rc Resources) *IPConstraints { - return &IPConstraints{ - iplimit: rc.MaxReservationsPerIP, - asnlimit: rc.MaxReservationsPerASN, - - peers: make(map[peer.ID]net.IP), - ips: make(map[string]map[peer.ID]struct{}), - asns: make(map[string]map[peer.ID]struct{}), - } -} - -// AddReservation adds a reservation for a given peer with a given multiaddr. -// If adding this reservation violates IP constraints, an error is returned. -func (ipcs *IPConstraints) AddReservation(p peer.ID, a ma.Multiaddr) error { - ip, err := manet.ToIP(a) - if err != nil { - return ErrNoIP - } - - ips := ip.String() - peersInIP := ipcs.ips[ips] - if len(peersInIP) >= ipcs.iplimit { - return ErrTooManyPeersInIP - } - - var peersInAsn map[peer.ID]struct{} - asn, _ := asnutil.Store.AsnForIPv6(ip) - peersInAsn = ipcs.asns[asn] - if len(peersInAsn) >= ipcs.asnlimit { - return ErrTooManyPeersInASN - } - - ipcs.peers[p] = ip - - if peersInIP == nil { - peersInIP = make(map[peer.ID]struct{}) - ipcs.ips[ips] = peersInIP - } - peersInIP[p] = struct{}{} - - if asn != "" { - if peersInAsn == nil { - peersInAsn = make(map[peer.ID]struct{}) - ipcs.asns[asn] = peersInAsn - } - peersInAsn[p] = struct{}{} - } - - return nil -} - -// RemoveReservation removes a peer from the constraints. -func (ipcs *IPConstraints) RemoveReservation(p peer.ID) { - ip, ok := ipcs.peers[p] - if !ok { - return - } - - ips := ip.String() - asn, _ := asnutil.Store.AsnForIPv6(ip) - - delete(ipcs.peers, p) - - peersInIP, ok := ipcs.ips[ips] - if ok { - delete(peersInIP, p) - if len(peersInIP) == 0 { - delete(ipcs.ips, ips) - } - } - - peersInAsn, ok := ipcs.asns[asn] - if ok { - delete(peersInAsn, p) - if len(peersInAsn) == 0 { - delete(ipcs.asns, asn) - } - } -} diff --git a/p2p/host/circuitv2/relay/relay.go b/p2p/host/circuitv2/relay/relay.go index d03aba9d7d..8ddddd1774 100644 --- a/p2p/host/circuitv2/relay/relay.go +++ b/p2p/host/circuitv2/relay/relay.go @@ -41,10 +41,10 @@ type Relay struct { ctx context.Context cancel func() - host host.Host - rc Resources - acl ACLFilter - ipcs *IPConstraints + host host.Host + rc Resources + acl ACLFilter + constraints *constraints mx sync.Mutex rsvp map[peer.ID]time.Time @@ -74,7 +74,7 @@ func New(h host.Host, opts ...Option) (*Relay, error) { } } - r.ipcs = NewIPConstraints(r.rc) + r.constraints = newConstraints(&r.rc) r.selfAddr = ma.StringCast(fmt.Sprintf("/p2p/%s", h.ID())) h.SetStreamHandler(proto.ProtoIDv2Hop, r.handleStream) @@ -153,14 +153,7 @@ func (r *Relay) handleReserve(s network.Stream) { _, exists := r.rsvp[p] if !exists { - active := len(r.rsvp) - if active >= r.rc.MaxReservations { - r.mx.Unlock() - log.Debugf("refusing relay reservation for %s; too many reservations", p) - r.handleError(s, pbv2.Status_RESERVATION_REFUSED) - return - } - if err := r.ipcs.AddReservation(p, a); err != nil { + 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) @@ -493,7 +486,6 @@ func (r *Relay) gc() { for p, expire := range r.rsvp { if expire.Before(now) { delete(r.rsvp, p) - r.ipcs.RemoveReservation(p) r.host.ConnManager().UntagPeer(p, "relay-reservation") } } @@ -515,5 +507,4 @@ func (r *Relay) disconnected(n network.Network, c network.Conn) { defer r.mx.Unlock() delete(r.rsvp, p) - r.ipcs.RemoveReservation(p) } diff --git a/p2p/host/circuitv2/relay/resources.go b/p2p/host/circuitv2/relay/resources.go index 345b527142..0ae0169899 100644 --- a/p2p/host/circuitv2/relay/resources.go +++ b/p2p/host/circuitv2/relay/resources.go @@ -20,8 +20,11 @@ type Resources struct { // 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 4. + // IP address; default is 8. MaxReservationsPerIP int // MaxReservationsPerASN is the maximum number of reservations origination from the same // ASN; default is 32 @@ -48,8 +51,9 @@ func DefaultResources() Resources { MaxCircuits: 16, BufferSize: 2048, - MaxReservationsPerIP: 4, - MaxReservationsPerASN: 32, + MaxReservationsPerPeer: 4, + MaxReservationsPerIP: 8, + MaxReservationsPerASN: 32, } } diff --git a/p2p/host/circuitv2/test/ipcs_test.go b/p2p/host/circuitv2/test/ipcs_test.go deleted file mode 100644 index db96e09143..0000000000 --- a/p2p/host/circuitv2/test/ipcs_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package test - -import ( - "fmt" - "net" - "testing" - - "github.com/libp2p/go-libp2p/p2p/host/circuitv2/relay" - - "github.com/libp2p/go-libp2p-core/peer" - - ma "github.com/multiformats/go-multiaddr" -) - -func TestIPConstraints(t *testing.T) { - ipcs := relay.NewIPConstraints(relay.Resources{ - MaxReservationsPerIP: 1, - MaxReservationsPerASN: 2, - }) - - peerA := peer.ID("A") - peerB := peer.ID("B") - peerC := peer.ID("C") - peerD := peer.ID("D") - peerE := peer.ID("E") - - ipA := net.ParseIP("1.2.3.4") - ipB := ipA - ipC := net.ParseIP("2001:200::1") - ipD := net.ParseIP("2001:200::2") - ipE := net.ParseIP("2001:200::3") - - err := ipcs.AddReservation(peerA, ma.StringCast(fmt.Sprintf("/ip4/%s/tcp/1234", ipA))) - if err != nil { - t.Fatal(err) - } - - err = ipcs.AddReservation(peerB, ma.StringCast(fmt.Sprintf("/ip4/%s/tcp/1234", ipB))) - if err != relay.ErrTooManyPeersInIP { - t.Fatalf("unexpected error: %s", err) - } - - ipcs.RemoveReservation(peerA) - err = ipcs.AddReservation(peerB, ma.StringCast(fmt.Sprintf("/ip4/%s/tcp/1234", ipB))) - if err != nil { - t.Fatal(err) - } - - err = ipcs.AddReservation(peerC, ma.StringCast(fmt.Sprintf("/ip6/%s/tcp/1234", ipC))) - if err != nil { - t.Fatal(err) - } - - err = ipcs.AddReservation(peerD, ma.StringCast(fmt.Sprintf("/ip6/%s/tcp/1234", ipD))) - if err != nil { - t.Fatal(err) - } - - err = ipcs.AddReservation(peerE, ma.StringCast(fmt.Sprintf("/ip6/%s/tcp/1234", ipE))) - if err != relay.ErrTooManyPeersInASN { - t.Fatalf("unexpected error: %s", err) - } - - ipcs.RemoveReservation(peerD) - err = ipcs.AddReservation(peerE, ma.StringCast(fmt.Sprintf("/ip6/%s/tcp/1234", ipE))) - if err != nil { - t.Fatal(err) - } -}