From d663611000188b2cdd81de7c3f826dc969c8f002 Mon Sep 17 00:00:00 2001 From: sukun Date: Wed, 12 Apr 2023 14:34:44 +0530 Subject: [PATCH 01/25] add autonat v2 spec --- autonat/README.md | 7 ++ autonat/autonat-v1.md | 3 - autonat/autonat-v2.md | 163 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 autonat/README.md create mode 100644 autonat/autonat-v2.md diff --git a/autonat/README.md b/autonat/README.md new file mode 100644 index 000000000..63f376fba --- /dev/null +++ b/autonat/README.md @@ -0,0 +1,7 @@ +# NAT Discovery +> How we detect if we're behind a NAT. + + +Specifications: +- [autonat v1](autonat-v1.md) +- [autonat v2](autonat-v2.md) diff --git a/autonat/autonat-v1.md b/autonat/autonat-v1.md index 33763eb97..65c2eebce 100644 --- a/autonat/autonat-v1.md +++ b/autonat/autonat-v1.md @@ -1,6 +1,3 @@ -# NAT Discovery -> How we detect if we're behind a NAT. - | Lifecycle Stage | Maturity | Status | Latest Revision | |-----------------|----------------|--------|-----------------| | 3A | Recommendation | Active | r1, 2023-02-16 | diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md new file mode 100644 index 000000000..976622ebb --- /dev/null +++ b/autonat/autonat-v2.md @@ -0,0 +1,163 @@ +# AutonatV2: spec + + +| Lifecycle Stage | Maturity | Status | Latest Revision | +|-----------------|--------------------------|--------|-----------------| +| 1A | Working Draft | Active | r1, 2023-04-12 | + +Authors: [@sukunrt] + +Interest Group: [@marten-seemann], [@marcopolo], [@mxinden] + +[@sukunrt]: https://github.com/sukunrt +[@marten-seemann]: https://github.com/marten-seemann +[@mxinden]: https://github.com/mxinden +[@marcopolo]: https://github.com/marcopolo + + +## Overview + +A priori, a node cannot know if it is behind a NAT / firewall or if it is +publicly reachable. Knowing its NAT status is essential for the node to be +well-behaved in the network: A node that's behind a doesn't need to +advertise its (undialable) addresses to the rest of the network, preventing +superfluous dials from other peers. Furthermore, it might actively seek to +improve its connectivity by finding a relay server, which would allow other +peers to establish a relayed connection. + +`autonat v2` allows nodes to determine reachability for individual addresses. +Using `autonat v2` nodes can build an address pipeline where they can test +individual addresses discovered by different sources like identify, upnp +mappings, circuit addresses etc for reachability. + +Compared to `autonat v1` there are two major differences +1. `autonat v1` allowed testing reachability for the node. `autonat v2` allows for testing reachability for an individual address +2. `autonat v2` provides a mechanism for nodes to verify whether the peer +actually successfully dialled an address. + + +## AutoNAT V2 Protocol + +A node wishing to determine reachability of a particular adddress sends a +`DialRequest` message to peer B on a stream with protocol ID +`/libp2p/autonat/2.0.0/dial-request`. This `DialRequest` message has the +address and a uint64 nonce. + +Upon receiving this message the peer attempts to dial the address and opens a +stream with Protocol ID `/libp2p/autonat/2.0.0/dial-attempt` and sends a +`DialAttempt` message with the nonce received in the `DialRequest`. The peer +MUST NOT dial any address other than the address provided in the `DialRequest` +message. + +Upon completion of the dial attempt, the peer sends a `DialResponse` to the +initiator node on the `/libp2p/autonat/2.0.0/dial-request` stream. + +The initiator SHOULD check that the nonce received in the `Dial` message is the +same as the nonce the initiator sent in the `DialRequest` message. If the nonce +received in the `Dial` message is different the initiator MUST discard this +`DialResponse` + +### Requirements for ResponseStatus + +The `ResponseStatus` sent by the peer in the `DialResponse` message MUST be set +according to the following requirements + +`OK`: the peer was able to dial the address successfully. +`E_DIAL_ERROR`: the peer attempted a dial and was unable to connect. +`E_DIAL_REFUSED`: the peer could have dialed the address but didn't attempt a +dial because of rate limiting, resource limit reached or blacklisting. +`E_TRANSPORT_NOT_SUPPORTED`: the peer didn't dial because it didn't have the +ability to dial the requested transport. +`E_BAD_REQUEST`: the peer didn't dial because it was unable to decode the +message. This includes inability to decode the requested address to dial. +`E_INTERNAL_ERROR`: error not classified within the above error codes occured +on peer that prevented it from completing the request. + +Implementations MUST count `OK` as a successful dial and MUST only count +`E_DIAL_ERROR` as an unsuccessful dial. Implementations MUST discard error codes +other than these two in calculating the reachability of the requested address. + +### Consideration for DDOS Prevention + +In order to prevent attacks like the one described in [RFC 3489, Section +12.1.1](https://www.rfc-editor.org/rfc/rfc3489#section-12.1.1) (see excerpt +below), implementations MUST NOT dial any multiaddress unless it is based on the +IP address the requesting node is observed as. This restriction as well implies +that implementations MUST NOT accept dial requests via relayed connections as +one can not validate the IP address of the requesting node. + +> RFC 3489 12.1.1 Attack I: DDOS Against a Target +> +> In this case, the attacker provides a large number of clients with the same +> faked MAPPED-ADDRESS that points to the intended target. This will trick all +> the STUN clients into thinking that their addresses are equal to that of the +> target. The clients then hand out that address in order to receive traffic on +> it (for example, in SIP or H.323 messages). However, all of that traffic +> becomes focused at the intended target. The attack can provide substantial +> amplification, especially when used with clients that are using STUN to enable +> multimedia applications. + + +## Implementation Suggestions + +For any given address, implementations SHOULD do the following +- periodically recheck reachability status +- query multiple peers to determine reachability + +The suggested heuristic for implementations is to consider an address +reachable if more than 3 peers report a successful dial and to consider an +address unreachable if more than 3 peers report unsuccessful dials. + +Implementations are free to use different heuristics than this one + + +## Protobufs + +All RPC messages sent over a stream are prefixed with the message length in +bytes, encoded as an unsigned variable length integer as defined by the +[multiformats unsigned-varint spec][uvarint-spec]. + +```proto +syntax = "proto3"; + +message Message { + enum MessageType { + DIAL_REQUEST = 0; + DIAL_RESPONSE = 1; + DIAL_ATTEMPT = 2; + } + + enum ResponseStatus { + OK = 0; + E_DIAL_ERROR = 100; + E_DIAL_REFUSED = 101; + E_TRANSPORT_NOT_SUPPORTED = 102; + E_BAD_REQUEST = 200; + E_INTERNAL_ERROR = 300; + } + + message DialRequest { + bytes addr = 1; + fixed64 nonce = 2; + } + + message DialResponse { + ResponseStatus status = 1; + string statusText = 2; + } + + message DialAttempt { + fixed64 nonce = 1; + } + + MessageType type = 1; + DialRequest dialRequest = 2; + DialResponse dialResponse = 3; + DialAttempt dialAttempt = 4; +} +``` + +[uvarint-spec]: https://github.com/multiformats/unsigned-varint + + + From 1db86131d8dc14092f86eab1c5cc5733409ce9eb Mon Sep 17 00:00:00 2001 From: sukun Date: Sat, 15 Apr 2023 15:11:17 +0530 Subject: [PATCH 02/25] use priority ordered list in requests for autonat-v2 --- autonat/autonat-v2.md | 167 ++++++++++++++++++++++++------------------ 1 file changed, 97 insertions(+), 70 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 976622ebb..2d225e76b 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -3,7 +3,7 @@ | Lifecycle Stage | Maturity | Status | Latest Revision | |-----------------|--------------------------|--------|-----------------| -| 1A | Working Draft | Active | r1, 2023-04-12 | +| 1A | Working Draft | Active | r2, 2023-04-15 | Authors: [@sukunrt] @@ -19,71 +19,89 @@ Interest Group: [@marten-seemann], [@marcopolo], [@mxinden] A priori, a node cannot know if it is behind a NAT / firewall or if it is publicly reachable. Knowing its NAT status is essential for the node to be -well-behaved in the network: A node that's behind a doesn't need to -advertise its (undialable) addresses to the rest of the network, preventing +well-behaved in the network: A node that's behind a NAT / firewall doesn't need +to advertise its (undialable) addresses to the rest of the network, preventing superfluous dials from other peers. Furthermore, it might actively seek to improve its connectivity by finding a relay server, which would allow other peers to establish a relayed connection. -`autonat v2` allows nodes to determine reachability for individual addresses. -Using `autonat v2` nodes can build an address pipeline where they can test -individual addresses discovered by different sources like identify, upnp -mappings, circuit addresses etc for reachability. +In `autonat v2` client sends a priority ordered list of addresses. On receiving +this list the server dials the first address on the list that it is capable of +dialing. `autonat v2` allows nodes to determine reachability for individual +addresses. Using `autonat v2` nodes can build an address pipeline where they can +test individual addresses discovered by different sources like identify, upnp +mappings, circuit addresses etc for reachability. Having a priority ordered list +of addresses provides the ability to verify low priority addresses. +Implementations can generate low priority address guesses and add them to +requests for high priority addresses as a nice to have. Compared to `autonat v1` there are two major differences -1. `autonat v1` allowed testing reachability for the node. `autonat v2` allows for testing reachability for an individual address -2. `autonat v2` provides a mechanism for nodes to verify whether the peer +1. `autonat v1` allowed testing reachability for the node. `autonat v2` allows +testing reachability for an individual address +2. `autonat v2` provides a mechanism for nodes to verify whether the peer actually successfully dialled an address. ## AutoNAT V2 Protocol -A node wishing to determine reachability of a particular adddress sends a -`DialRequest` message to peer B on a stream with protocol ID -`/libp2p/autonat/2.0.0/dial-request`. This `DialRequest` message has the -address and a uint64 nonce. +A node wishing to determine reachability of its adddresses sends a `DialRequest` +message to a peer on a stream with protocol ID +`/libp2p/autonat/2.0.0/dial-request`. -Upon receiving this message the peer attempts to dial the address and opens a -stream with Protocol ID `/libp2p/autonat/2.0.0/dial-attempt` and sends a -`DialAttempt` message with the nonce received in the `DialRequest`. The peer -MUST NOT dial any address other than the address provided in the `DialRequest` -message. +This `DialRequest` message has a list of `AddressDialRequest`s. Each item in +this list contains an address and a fixed64 nonce. The list is ordered in +descending order of priority for verfication. -Upon completion of the dial attempt, the peer sends a `DialResponse` to the -initiator node on the `/libp2p/autonat/2.0.0/dial-request` stream. +Upon receiving this message the peer attempts to dial the first address from the +list that it is capable of dialing. It dials this address, opens a stream with +Protocol ID `/libp2p/autonat/2.0.0/dial-attempt` and sends a `DialAttempt` +message with the nonce received in the corresponding `AddressDialRequest`. The +peer MUST dial the first address in the list it is capable of dialing. The peer +MUST NOT dial any address not in the list of addresses sent in the request. The +peer MUST ignore errors on `/libp2p/autonat/2.0.0/dial-attempt` stream + +Upon completion of the dial attempt, the peer sends a `DialResponse` message to +the initiator node on the `/libp2p/autonat/2.0.0/dial-request` stream with the +address that it attempted to dial and the appropriate `ResponseStatus`. see +[Requirements For ResponseStatus](#requirements-for-responsestatus) + +The initiator SHOULD check that the nonce received in the `DialAttempt` is the +same as the nonce the initiator sent in the `AddressDialRequest` for the address +received in `DialResponse`. If the nonce is different, the initiator MUST +discard this response. -The initiator SHOULD check that the nonce received in the `Dial` message is the -same as the nonce the initiator sent in the `DialRequest` message. If the nonce -received in the `Dial` message is different the initiator MUST discard this -`DialResponse` ### Requirements for ResponseStatus -The `ResponseStatus` sent by the peer in the `DialResponse` message MUST be set +On receiving a `DialRequest` the peer selects the first address on the list it +is capable of dialing. This address is referred to as _addr_. The +`ResponseStatus` sent by the peer in the `DialResponse` message MUST be set according to the following requirements -`OK`: the peer was able to dial the address successfully. -`E_DIAL_ERROR`: the peer attempted a dial and was unable to connect. -`E_DIAL_REFUSED`: the peer could have dialed the address but didn't attempt a -dial because of rate limiting, resource limit reached or blacklisting. -`E_TRANSPORT_NOT_SUPPORTED`: the peer didn't dial because it didn't have the -ability to dial the requested transport. -`E_BAD_REQUEST`: the peer didn't dial because it was unable to decode the -message. This includes inability to decode the requested address to dial. -`E_INTERNAL_ERROR`: error not classified within the above error codes occured -on peer that prevented it from completing the request. - -Implementations MUST count `OK` as a successful dial and MUST only count -`E_DIAL_ERROR` as an unsuccessful dial. Implementations MUST discard error codes -other than these two in calculating the reachability of the requested address. +`OK`: the peer was able to dial _addr_ successfully. + +`E_DIAL_ERROR`: the peer attempted to dial _addr_ and was unable to connect. + +`E_DIAL_REFUSED`: the peer didn't attempt a dial because of rate limiting, +resource limit reached or blacklisting. + +`E_TRANSPORT_NOT_SUPPORTED`: the peer didn't have the ability to dial any of the +requested addresses. + +`E_BAD_REQUEST`: the peer didn't attempt a dial because it was unable to decode +the message. This includes inability to decode the requested address to dial. + +`E_INTERNAL_ERROR`: error not classified within the above error codes occured on +peer that prevented it from completing the request. + ### Consideration for DDOS Prevention In order to prevent attacks like the one described in [RFC 3489, Section 12.1.1](https://www.rfc-editor.org/rfc/rfc3489#section-12.1.1) (see excerpt below), implementations MUST NOT dial any multiaddress unless it is based on the -IP address the requesting node is observed as. This restriction as well implies -that implementations MUST NOT accept dial requests via relayed connections as +IP address the requesting node is observed as. This restriction as well implies +that implementations MUST NOT accept dial requests via relayed connections as one can not validate the IP address of the requesting node. > RFC 3489 12.1.1 Attack I: DDOS Against a Target @@ -104,18 +122,24 @@ For any given address, implementations SHOULD do the following - periodically recheck reachability status - query multiple peers to determine reachability -The suggested heuristic for implementations is to consider an address -reachable if more than 3 peers report a successful dial and to consider an -address unreachable if more than 3 peers report unsuccessful dials. +The suggested heuristic for implementations is to consider an address reachable +if more than 3 peers report a successful dial and to consider an address +unreachable if more than 3 peers report unsuccessful dials. Implementations are free to use different heuristics than this one -## Protobufs +## RPC Messages All RPC messages sent over a stream are prefixed with the message length in bytes, encoded as an unsigned variable length integer as defined by the -[multiformats unsigned-varint spec][uvarint-spec]. +[multiformats unsigned-varint spec][uvarint-spec]. + +All RPC messages are of type `Message`. A `DialRequest` message is sent as a +`Message` with the `dialRequest` field set and the `type` field set to +`DIAL_REQUEST`. Other message types `DialResponse` and `DialAttempt` are handled +similarly. + ```proto syntax = "proto3"; @@ -127,37 +151,40 @@ message Message { DIAL_ATTEMPT = 2; } - enum ResponseStatus { - OK = 0; - E_DIAL_ERROR = 100; - E_DIAL_REFUSED = 101; - E_TRANSPORT_NOT_SUPPORTED = 102; - E_BAD_REQUEST = 200; - E_INTERNAL_ERROR = 300; - } + MessageType type = 1; + DialRequest dialRequest = 2; + DialResponse dialResponse = 3; + DialAttempt dialAttempt = 4; +} - message DialRequest { - bytes addr = 1; - fixed64 nonce = 2; - } +message AddressDialRequest { + bytes addr = 1; + fixed64 nonce = 2; +} - message DialResponse { - ResponseStatus status = 1; - string statusText = 2; - } +message DialRequest { + repeated AddressDialRequest addressDialRequests = 1; +} - message DialAttempt { +message DialAttempt { fixed64 nonce = 1; - } +} - MessageType type = 1; - DialRequest dialRequest = 2; - DialResponse dialResponse = 3; - DialAttempt dialAttempt = 4; +message DialResponse { + enum ResponseStatus { + OK = 0; + E_DIAL_ERROR = 100; + E_DIAL_REFUSED = 101; + E_TRANSPORT_NOT_SUPPORTED = 102; + E_BAD_REQUEST = 200; + E_INTERNAL_ERROR = 300; + } + + ResponseStatus status = 1; + string statusText = 2; + bytes addr = 3; } ``` [uvarint-spec]: https://github.com/multiformats/unsigned-varint - - From 0ff8ac6638e2ea8319cea43cfad88a69c89fa6dc Mon Sep 17 00:00:00 2001 From: sukun Date: Fri, 21 Apr 2023 20:08:21 +0530 Subject: [PATCH 03/25] only send index of the dialed address --- autonat/autonat-v2.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 2d225e76b..c3ea806cf 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -33,7 +33,10 @@ test individual addresses discovered by different sources like identify, upnp mappings, circuit addresses etc for reachability. Having a priority ordered list of addresses provides the ability to verify low priority addresses. Implementations can generate low priority address guesses and add them to -requests for high priority addresses as a nice to have. +requests for high priority addresses as a nice to have. This is especially +helpful when introducing a new transport. Initially, such a transport will not +be widely supported in the network. Requests for verifying such addresses can be +reused to get information about other addresses Compared to `autonat v1` there are two major differences 1. `autonat v1` allowed testing reachability for the node. `autonat v2` allows @@ -56,18 +59,18 @@ Upon receiving this message the peer attempts to dial the first address from the list that it is capable of dialing. It dials this address, opens a stream with Protocol ID `/libp2p/autonat/2.0.0/dial-attempt` and sends a `DialAttempt` message with the nonce received in the corresponding `AddressDialRequest`. The -peer MUST dial the first address in the list it is capable of dialing. The peer -MUST NOT dial any address not in the list of addresses sent in the request. The -peer MUST ignore errors on `/libp2p/autonat/2.0.0/dial-attempt` stream +peer MUST NOT dial any address other than the first address in the list that it +is capable of dialing. Upon completion of the dial attempt, the peer sends a `DialResponse` message to the initiator node on the `/libp2p/autonat/2.0.0/dial-request` stream with the -address that it attempted to dial and the appropriate `ResponseStatus`. see -[Requirements For ResponseStatus](#requirements-for-responsestatus) +index(0 based) of the address that it attempted to dial and the appropriate +`ResponseStatus`. see [Requirements For +ResponseStatus](#requirements-for-responsestatus) -The initiator SHOULD check that the nonce received in the `DialAttempt` is the +The initiator MUST check that the nonce received in the `DialAttempt` is the same as the nonce the initiator sent in the `AddressDialRequest` for the address -received in `DialResponse`. If the nonce is different, the initiator MUST +index received in `DialResponse`. If the nonce is different, the initiator MUST discard this response. @@ -182,7 +185,7 @@ message DialResponse { ResponseStatus status = 1; string statusText = 2; - bytes addr = 3; + int32 addrIdx = 3; } ``` From 62123df073eabbf0a8260be694c36eb59f43b302 Mon Sep 17 00:00:00 2001 From: sukun Date: Tue, 25 Apr 2023 12:36:34 +0530 Subject: [PATCH 04/25] Improve naming for messages --- autonat/autonat-v2.md | 66 +++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index c3ea806cf..1e640efc3 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -49,35 +49,35 @@ actually successfully dialled an address. A node wishing to determine reachability of its adddresses sends a `DialRequest` message to a peer on a stream with protocol ID -`/libp2p/autonat/2.0.0/dial-request`. +`/libp2p/autonat/2.0.0/dial`. -This `DialRequest` message has a list of `AddressDialRequest`s. Each item in +This `DialRequest` message has a list of `Candidate`s. Each item in this list contains an address and a fixed64 nonce. The list is ordered in descending order of priority for verfication. -Upon receiving this message the peer attempts to dial the first address from the -list that it is capable of dialing. It dials this address, opens a stream with -Protocol ID `/libp2p/autonat/2.0.0/dial-attempt` and sends a `DialAttempt` -message with the nonce received in the corresponding `AddressDialRequest`. The -peer MUST NOT dial any address other than the first address in the list that it -is capable of dialing. +Upon receiving this message the peer attempts to dial the first candidate from +the list of candidates that it is capable of dialing. It dials the candidate +address, opens a stream with Protocol ID `/libp2p/autonat/2.0.0/attempt` and +sends a `DialAttempt` message with the candidate nonce. The peer MUST NOT dial +any candidate other than the first candidate in the list that it is capable of +dialing. Upon completion of the dial attempt, the peer sends a `DialResponse` message to -the initiator node on the `/libp2p/autonat/2.0.0/dial-request` stream with the -index(0 based) of the address that it attempted to dial and the appropriate +the initiator node on the `/libp2p/autonat/2.0.0/dial` stream with the +index(0 based) of the candidate that it attempted to dial and the appropriate `ResponseStatus`. see [Requirements For ResponseStatus](#requirements-for-responsestatus) The initiator MUST check that the nonce received in the `DialAttempt` is the -same as the nonce the initiator sent in the `AddressDialRequest` for the address +same as the nonce the initiator sent in the `Candidate` for the candidate index received in `DialResponse`. If the nonce is different, the initiator MUST discard this response. ### Requirements for ResponseStatus -On receiving a `DialRequest` the peer selects the first address on the list it -is capable of dialing. This address is referred to as _addr_. The +On receiving a `DialRequest` the peer selects the first candidate on the list it +is capable of dialing. This candidate address is referred to as _addr_. The `ResponseStatus` sent by the peer in the `DialResponse` message MUST be set according to the following requirements @@ -92,11 +92,12 @@ resource limit reached or blacklisting. requested addresses. `E_BAD_REQUEST`: the peer didn't attempt a dial because it was unable to decode -the message. This includes inability to decode the requested address to dial. +the message. `E_INTERNAL_ERROR`: error not classified within the above error codes occured on peer that prevented it from completing the request. +Implementations MUST discard responses with status codes they do not understand ### Consideration for DDOS Prevention @@ -138,39 +139,35 @@ All RPC messages sent over a stream are prefixed with the message length in bytes, encoded as an unsigned variable length integer as defined by the [multiformats unsigned-varint spec][uvarint-spec]. -All RPC messages are of type `Message`. A `DialRequest` message is sent as a -`Message` with the `dialRequest` field set and the `type` field set to -`DIAL_REQUEST`. Other message types `DialResponse` and `DialAttempt` are handled +All RPC messages on stream `/libp2p/autonat/2.0.0/dial` are of type +`DialMessage`. A `DialRequest` message is sent as a `DialMessage` with the `dialRequest` +field set and the `type` field set to `DIAL_REQUEST`. `DialResponse` is handled similarly. +On stream `/libp2p/autonat/2.0.0/attempt`, there is a single message type +`AttemptMessage` ```proto syntax = "proto3"; -message Message { - enum MessageType { +message DialMessage { + enum Type { DIAL_REQUEST = 0; DIAL_RESPONSE = 1; - DIAL_ATTEMPT = 2; } - MessageType type = 1; + Type type = 1; DialRequest dialRequest = 2; DialResponse dialResponse = 3; - DialAttempt dialAttempt = 4; } -message AddressDialRequest { +message Candidate { bytes addr = 1; fixed64 nonce = 2; } message DialRequest { - repeated AddressDialRequest addressDialRequests = 1; -} - -message DialAttempt { - fixed64 nonce = 1; + repeated Candidate candidates = 1; } message DialResponse { @@ -187,6 +184,19 @@ message DialResponse { string statusText = 2; int32 addrIdx = 3; } + +message AttemptMessage { + enum Type { + DIAL_ATTEMPT = 0; + } + + Type type = 1; + DialAttempt dialAttempt = 2; +} + +message DialAttempt { + fixed64 nonce = 1; +} ``` [uvarint-spec]: https://github.com/multiformats/unsigned-varint From 0771babb17eb9e33d5334c78cb658fdf7e687a7f Mon Sep 17 00:00:00 2001 From: sukun Date: Tue, 25 Apr 2023 17:54:38 +0530 Subject: [PATCH 05/25] add interaction diagram --- autonat/autonat-v2.md | 4 ++++ autonat/autonat-v2.plantuml | 19 +++++++++++++++++++ autonat/autonat-v2.svg | 1 + 3 files changed, 24 insertions(+) create mode 100644 autonat/autonat-v2.plantuml create mode 100644 autonat/autonat-v2.svg diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 1e640efc3..1bc103a6c 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -47,6 +47,10 @@ actually successfully dialled an address. ## AutoNAT V2 Protocol + +![Autonat V2 Interaction](autonat-v2.svg) + + A node wishing to determine reachability of its adddresses sends a `DialRequest` message to a peer on a stream with protocol ID `/libp2p/autonat/2.0.0/dial`. diff --git a/autonat/autonat-v2.plantuml b/autonat/autonat-v2.plantuml new file mode 100644 index 000000000..0722fc89e --- /dev/null +++ b/autonat/autonat-v2.plantuml @@ -0,0 +1,19 @@ +@startuml +participant Cli +participant Srv + +skinparam backgroundColor white +skinparam sequenceMessageAlign center + +== Dial Request Success== + +Cli -> Srv: [dial] DialRequest: (Addr1, Token1), (Addr2, Token2) +Srv -> Cli: [attempt] DialAttempt: Token2 +Srv -> Cli: [dial] DialResponse: STATUS: OK, CandidateIdx: 1 + +== Dial Request Failure== + +Cli -> Srv: [dial] DialRequest: (Addr1, Token1), (Addr2, Token2) +Srv ->x Cli: [attempt] DialAttempt: Token2 +Srv -> Cli: [dial] DialResponse: STATUS: E_DIAL_ERROR, CandidateIdx: 1 +@enduml diff --git a/autonat/autonat-v2.svg b/autonat/autonat-v2.svg new file mode 100644 index 000000000..c0df08821 --- /dev/null +++ b/autonat/autonat-v2.svg @@ -0,0 +1 @@ +CliCliSrvSrvDial Request Success[dial] DialRequest: (Addr1, Token1), (Addr2, Token2)[attempt] DialAttempt: Token2[dial] DialResponse: STATUS: OK, CandidateIdx: 1Dial Request Failure[dial] DialRequest: (Addr1, Token1), (Addr2, Token2)[attempt] DialAttempt: Token2[dial] DialResponse: STATUS: E_DIAL_ERROR, CandidateIdx: 1 \ No newline at end of file From 3e57202586dd7156d9f317f7483b844d3e82597a Mon Sep 17 00:00:00 2001 From: sukun Date: Thu, 27 Apr 2023 17:03:16 +0530 Subject: [PATCH 06/25] address review comments --- autonat/autonat-v2.md | 65 ++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 1bc103a6c..98f938ea9 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -27,16 +27,17 @@ peers to establish a relayed connection. In `autonat v2` client sends a priority ordered list of addresses. On receiving this list the server dials the first address on the list that it is capable of -dialing. `autonat v2` allows nodes to determine reachability for individual -addresses. Using `autonat v2` nodes can build an address pipeline where they can -test individual addresses discovered by different sources like identify, upnp -mappings, circuit addresses etc for reachability. Having a priority ordered list -of addresses provides the ability to verify low priority addresses. -Implementations can generate low priority address guesses and add them to -requests for high priority addresses as a nice to have. This is especially -helpful when introducing a new transport. Initially, such a transport will not -be widely supported in the network. Requests for verifying such addresses can be -reused to get information about other addresses +dialing. As the server dials _exactly_ one address from the list, `autonat v2` +allows nodes to determine reachability for individual addresses. Using `autonat +v2` nodes can build an address pipeline where they can test individual addresses +discovered by different sources like identify, upnp mappings, circuit addresses +etc for reachability. Having a priority ordered list of addresses provides the +ability to verify low priority addresses. Implementations can generate low +priority address guesses and add them to requests for high priority addresses as +a nice to have. This is especially helpful when introducing a new transport. +Initially, such a transport will not be widely supported in the network. +Requests for verifying such addresses can be reused to get information about +other addresses Compared to `autonat v1` there are two major differences 1. `autonat v1` allowed testing reachability for the node. `autonat v2` allows @@ -52,31 +53,34 @@ actually successfully dialled an address. A node wishing to determine reachability of its adddresses sends a `DialRequest` -message to a peer on a stream with protocol ID -`/libp2p/autonat/2.0.0/dial`. +message to a peer on a stream with protocol ID `/libp2p/autonat/2.0.0/dial`. +Each `DialRequest` is sent on a new stream. -This `DialRequest` message has a list of `Candidate`s. Each item in -this list contains an address and a fixed64 nonce. The list is ordered in -descending order of priority for verfication. +This `DialRequest` message has a list of `Candidate`s. Each item in this list +contains an address and a fixed64 nonce. The list is ordered in descending order +of priority for verfication. -Upon receiving this message the peer attempts to dial the first candidate from -the list of candidates that it is capable of dialing. It dials the candidate +Upon receiving this message the peer selects the first candidate from the list +of candidates that it is capable of dialing. The peer MUST NOT dial any +candidate other than this selected candidate. It dials the selected candidate's address, opens a stream with Protocol ID `/libp2p/autonat/2.0.0/attempt` and -sends a `DialAttempt` message with the candidate nonce. The peer MUST NOT dial -any candidate other than the first candidate in the list that it is capable of -dialing. +sends a `DialAttempt` message with the candidate nonce. The peer MUST close this +stream after sending the `DialAttempt` message. Upon completion of the dial attempt, the peer sends a `DialResponse` message to -the initiator node on the `/libp2p/autonat/2.0.0/dial` stream with the -index(0 based) of the candidate that it attempted to dial and the appropriate +the initiator node on the `/libp2p/autonat/2.0.0/dial` stream with the index(0 +based) of the candidate that it attempted to dial and the appropriate `ResponseStatus`. see [Requirements For ResponseStatus](#requirements-for-responsestatus) The initiator MUST check that the nonce received in the `DialAttempt` is the -same as the nonce the initiator sent in the `Candidate` for the candidate -index received in `DialResponse`. If the nonce is different, the initiator MUST +same as the nonce the initiator sent in the `Candidate` for the candidate index +received in `DialResponse`. If the nonce is different, the initiator MUST discard this response. +The peer MUST close the stream after sending the response. The initiator MUST +close the stream after receiving the response. + ### Requirements for ResponseStatus @@ -136,6 +140,10 @@ unreachable if more than 3 peers report unsuccessful dials. Implementations are free to use different heuristics than this one +Implementations SHOULD only verify reachability for private addresses as defined +in [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) with peers that are +on the same subnet + ## RPC Messages @@ -144,9 +152,9 @@ bytes, encoded as an unsigned variable length integer as defined by the [multiformats unsigned-varint spec][uvarint-spec]. All RPC messages on stream `/libp2p/autonat/2.0.0/dial` are of type -`DialMessage`. A `DialRequest` message is sent as a `DialMessage` with the `dialRequest` -field set and the `type` field set to `DIAL_REQUEST`. `DialResponse` is handled -similarly. +`DialMessage`. A `DialRequest` message is sent as a `DialMessage` with the +`dialRequest` field set and the `type` field set to `DIAL_REQUEST`. +`DialResponse` is handled similarly. On stream `/libp2p/autonat/2.0.0/attempt`, there is a single message type `AttemptMessage` @@ -185,8 +193,7 @@ message DialResponse { } ResponseStatus status = 1; - string statusText = 2; - int32 addrIdx = 3; + int32 addrIdx = 2; } message AttemptMessage { From f6def9afe46e6d7572f34ee42ed843e2980faa0e Mon Sep 17 00:00:00 2001 From: Sukun Date: Wed, 3 May 2023 16:40:52 +0530 Subject: [PATCH 07/25] allow autonat v2 to dial all ips (#542) --- autonat/autonat-v2.md | 71 +++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 98f938ea9..e744ede5c 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -44,6 +44,10 @@ Compared to `autonat v1` there are two major differences testing reachability for an individual address 2. `autonat v2` provides a mechanism for nodes to verify whether the peer actually successfully dialled an address. +3. `autonat v2` provides a mechanism for nodes to dial an IP address different +from the requesting node's observed IP address without risking amplification +attacks. `autonat v1` disallowed such dials to prevent amplification attacks. + ## AutoNAT V2 Protocol @@ -62,10 +66,19 @@ of priority for verfication. Upon receiving this message the peer selects the first candidate from the list of candidates that it is capable of dialing. The peer MUST NOT dial any -candidate other than this selected candidate. It dials the selected candidate's -address, opens a stream with Protocol ID `/libp2p/autonat/2.0.0/attempt` and -sends a `DialAttempt` message with the candidate nonce. The peer MUST close this -stream after sending the `DialAttempt` message. +candidate other than this selected candidate. If this selected candidate address +has an IP address different from the requesting node's observed IP address, peer +initiates the Amplification attack prevention mechanism (see [Amplification +Attack Prevention](#amplification-attack-prevention) ). On completion, the peer +proceeds to the next step. If the selected address has the same IP address as +the requesting node's observed IP address, peer directly proceeds to the next +step skipping Amplification Attack prevention steps. + + +The peer dials the selected candidate's address, opens a stream with Protocol ID +`/libp2p/autonat/2.0.0/attempt` and sends a `DialAttempt` message with the +candidate nonce. The peer MUST close this stream after sending the `DialAttempt` +message. Upon completion of the dial attempt, the peer sends a `DialResponse` message to the initiator node on the `/libp2p/autonat/2.0.0/dial` stream with the index(0 @@ -107,26 +120,24 @@ peer that prevented it from completing the request. Implementations MUST discard responses with status codes they do not understand -### Consideration for DDOS Prevention +### Amplification Attack Prevention -In order to prevent attacks like the one described in [RFC 3489, Section -12.1.1](https://www.rfc-editor.org/rfc/rfc3489#section-12.1.1) (see excerpt -below), implementations MUST NOT dial any multiaddress unless it is based on the -IP address the requesting node is observed as. This restriction as well implies -that implementations MUST NOT accept dial requests via relayed connections as -one can not validate the IP address of the requesting node. +When a client asks a server to dial an address that is not the client's observed +IP address, the server asks the client to send him some non trivial amount of +bytes as a cost to dial a different IP address. To make amplification attacks +unattractive, the number of bytes is decided such that it's sufficiently larger +than a new connection handshake cost. -> RFC 3489 12.1.1 Attack I: DDOS Against a Target -> -> In this case, the attacker provides a large number of clients with the same -> faked MAPPED-ADDRESS that points to the intended target. This will trick all -> the STUN clients into thinking that their addresses are equal to that of the -> target. The clients then hand out that address in order to receive traffic on -> it (for example, in SIP or H.323 messages). However, all of that traffic -> becomes focused at the intended target. The attack can provide substantial -> amplification, especially when used with clients that are using STUN to enable -> multimedia applications. +On receiving a `DialRequest`, the server selects the first address it is capable +of dialing. If this selected address has a IP different from the client's +observed ip, the server sends a `DialDataRequest` message with `numBytes` set to +a sufficiently large value on the `/libp2p/autonat/2.0.0/dial-request` stream +Upon receiving a `DialDataRequest` message, the client decides whether to accept +or reject the cost of dial. If the client rejects the cost, the client resets +the stream and the `DialRequest` is considered aborted. If the client accepts +the cost, the client starts transferring `numBytes` bytes to the server. The +server on receiving `numBytes` bytes proceeds to dial the candidate address. ## Implementation Suggestions @@ -154,7 +165,7 @@ bytes, encoded as an unsigned variable length integer as defined by the All RPC messages on stream `/libp2p/autonat/2.0.0/dial` are of type `DialMessage`. A `DialRequest` message is sent as a `DialMessage` with the `dialRequest` field set and the `type` field set to `DIAL_REQUEST`. -`DialResponse` is handled similarly. +`DialResponse` and `DialDataRequest` are handled similarly. On stream `/libp2p/autonat/2.0.0/attempt`, there is a single message type `AttemptMessage` @@ -164,13 +175,15 @@ syntax = "proto3"; message DialMessage { enum Type { - DIAL_REQUEST = 0; - DIAL_RESPONSE = 1; + DIAL_REQUEST = 0; + DIAL_RESPONSE = 1; + DIAL_DATA_REQUEST = 2; } Type type = 1; DialRequest dialRequest = 2; DialResponse dialResponse = 3; + DialDataRequest dialDataRequest = 4; } message Candidate { @@ -182,6 +195,10 @@ message DialRequest { repeated Candidate candidates = 1; } +message DialDataRequest { + uint64 numBytes = 1; +} + message DialResponse { enum ResponseStatus { OK = 0; @@ -190,10 +207,10 @@ message DialResponse { E_TRANSPORT_NOT_SUPPORTED = 102; E_BAD_REQUEST = 200; E_INTERNAL_ERROR = 300; - } + } - ResponseStatus status = 1; - int32 addrIdx = 2; + ResponseStatus status = 1; + int32 addrIdx = 2; } message AttemptMessage { From b769d79a1ec2acff64a104e96ffcd34f891b11db Mon Sep 17 00:00:00 2001 From: sukun Date: Wed, 3 May 2023 18:46:34 +0530 Subject: [PATCH 08/25] use oneof for messages --- autonat/autonat-v2.md | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index e744ede5c..efbadb190 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -164,26 +164,20 @@ bytes, encoded as an unsigned variable length integer as defined by the All RPC messages on stream `/libp2p/autonat/2.0.0/dial` are of type `DialMessage`. A `DialRequest` message is sent as a `DialMessage` with the -`dialRequest` field set and the `type` field set to `DIAL_REQUEST`. -`DialResponse` and `DialDataRequest` are handled similarly. - -On stream `/libp2p/autonat/2.0.0/attempt`, there is a single message type -`AttemptMessage` +`dialRequest` field set to `DialRequest` message. `DialResponse` and +`DialDataRequest` are handled similarly. On stream +`/libp2p/autonat/2.0.0/attempt`, there is a single message type `AttemptMessage` +where `DialAttempt` is handled similarly. ```proto syntax = "proto3"; message DialMessage { - enum Type { - DIAL_REQUEST = 0; - DIAL_RESPONSE = 1; - DIAL_DATA_REQUEST = 2; + oneof msg { + DialRequest dialRequest = 1; + DialResponse dialResponse = 2; + DialDataRequest dialDataRequest = 3; } - - Type type = 1; - DialRequest dialRequest = 2; - DialResponse dialResponse = 3; - DialDataRequest dialDataRequest = 4; } message Candidate { @@ -214,12 +208,9 @@ message DialResponse { } message AttemptMessage { - enum Type { - DIAL_ATTEMPT = 0; + oneof msg { + DialAttempt dialAttempt = 1; } - - Type type = 1; - DialAttempt dialAttempt = 2; } message DialAttempt { From e4efaae16b4d792f3af6297ff874484b4b4eec10 Mon Sep 17 00:00:00 2001 From: sukun Date: Mon, 15 May 2023 18:15:55 +0530 Subject: [PATCH 09/25] use a single nonce in DialRequest --- ...2-amplification-attack-prevention.plantuml | 17 ++ ...nat-v2-amplification-attack-prevention.svg | 1 + autonat/autonat-v2.md | 219 ++++++++++-------- autonat/autonat-v2.plantuml | 17 +- autonat/autonat-v2.svg | 2 +- 5 files changed, 154 insertions(+), 102 deletions(-) create mode 100644 autonat/autonat-v2-amplification-attack-prevention.plantuml create mode 100644 autonat/autonat-v2-amplification-attack-prevention.svg diff --git a/autonat/autonat-v2-amplification-attack-prevention.plantuml b/autonat/autonat-v2-amplification-attack-prevention.plantuml new file mode 100644 index 000000000..c960d37ba --- /dev/null +++ b/autonat/autonat-v2-amplification-attack-prevention.plantuml @@ -0,0 +1,17 @@ +@startuml +participant Cli +participant Srv + +skinparam sequenceMessageAlign center +skinparam defaultFontName monospaced + + +== Amplification Attack Prevention == + +Cli -> Srv: [dial] DialRequest:{nonce: 0xabcd, addrs: (addr1, addr2, addr3)} +Srv -> Cli: [dial] DialDataRequest:{addrIdx: 1, numBytes: 120k} +Cli -> Srv: [dial] {120k bytes} +Srv -> Cli: [attempt]addr2 DialAttempt:{nonce: 0xabcd} +Srv -> Cli: [dial] DialResponse:{status: OK, dialStatuses:(E_TRANSPORT_NOT_SUPPORTED, OK)} + +@enduml diff --git a/autonat/autonat-v2-amplification-attack-prevention.svg b/autonat/autonat-v2-amplification-attack-prevention.svg new file mode 100644 index 000000000..3081117fc --- /dev/null +++ b/autonat/autonat-v2-amplification-attack-prevention.svg @@ -0,0 +1 @@ +CliCliSrvSrvAmplification Attack Prevention[dial] DialRequest:{nonce: 0xabcd, addrs: (addr1, addr2, addr3)}[dial] DialDataRequest:{addrIdx: 1, numBytes: 120k}[dial] {120k bytes}[attempt]addr2 DialAttempt:{nonce: 0xabcd}[dial] DialResponse:{status: OK, dialStatuses:(E_TRANSPORT_NOT_SUPPORTED, OK)} \ No newline at end of file diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index efbadb190..205403daa 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -25,11 +25,15 @@ superfluous dials from other peers. Furthermore, it might actively seek to improve its connectivity by finding a relay server, which would allow other peers to establish a relayed connection. -In `autonat v2` client sends a priority ordered list of addresses. On receiving -this list the server dials the first address on the list that it is capable of -dialing. As the server dials _exactly_ one address from the list, `autonat v2` -allows nodes to determine reachability for individual addresses. Using `autonat -v2` nodes can build an address pipeline where they can test individual addresses +In `autonat v2` client sends a request with a priority ordered list of addresses +and a nonce. On receiving this request the server dials the first address in the +list that it is capable of dialing and provides the nonce. Upon completion of +the dial, the server responds to the client with the response containing the +dial outcome. + +As the server dials _exactly_ one address from the list, `autonat v2` allows +nodes to determine reachability for individual addresses. Using `autonat v2` +nodes can build an address pipeline where they can test individual addresses discovered by different sources like identify, upnp mappings, circuit addresses etc for reachability. Having a priority ordered list of addresses provides the ability to verify low priority addresses. Implementations can generate low @@ -39,9 +43,13 @@ Initially, such a transport will not be widely supported in the network. Requests for verifying such addresses can be reused to get information about other addresses -Compared to `autonat v1` there are two major differences +The client can verify that the server did successfully dial an address of the +same transport as it reported in the response by checking the local address of +the connection on which the nonce was received on. + +Compared to `autonat v1` there are three major differences 1. `autonat v1` allowed testing reachability for the node. `autonat v2` allows -testing reachability for an individual address +testing reachability for an individual address. 2. `autonat v2` provides a mechanism for nodes to verify whether the peer actually successfully dialled an address. 3. `autonat v2` provides a mechanism for nodes to dial an IP address different @@ -52,76 +60,106 @@ attacks. `autonat v1` disallowed such dials to prevent amplification attacks. ## AutoNAT V2 Protocol - ![Autonat V2 Interaction](autonat-v2.svg) - A node wishing to determine reachability of its adddresses sends a `DialRequest` -message to a peer on a stream with protocol ID `/libp2p/autonat/2.0.0/dial`. -Each `DialRequest` is sent on a new stream. - -This `DialRequest` message has a list of `Candidate`s. Each item in this list -contains an address and a fixed64 nonce. The list is ordered in descending order -of priority for verfication. - -Upon receiving this message the peer selects the first candidate from the list -of candidates that it is capable of dialing. The peer MUST NOT dial any -candidate other than this selected candidate. If this selected candidate address -has an IP address different from the requesting node's observed IP address, peer -initiates the Amplification attack prevention mechanism (see [Amplification -Attack Prevention](#amplification-attack-prevention) ). On completion, the peer +message to a peer on a stream with protocol ID `/libp2p/autonat/2/dial`. Each +`DialRequest` is sent on a new stream. + +This `DialRequest` message has a list of addresses and a fixed64 `nonce`. The +list is ordered in descending order of priority for verification. + +Upon receiving this request, the peer selects the first address from the list of +addresses that it is capable of dialing. The peer MUST NOT dial any address +other than this one. If this selected address has an IP address different from +the requesting node's observed IP address, peer initiates the Amplification +attack prevention mechanism (see [Amplification Attack +Prevention](#amplification-attack-prevention) ). On completion, the peer proceeds to the next step. If the selected address has the same IP address as -the requesting node's observed IP address, peer directly proceeds to the next -step skipping Amplification Attack prevention steps. - +the requesting node's observed IP address, peer proceeds to the next step +skipping Amplification Attack prevention steps. -The peer dials the selected candidate's address, opens a stream with Protocol ID -`/libp2p/autonat/2.0.0/attempt` and sends a `DialAttempt` message with the -candidate nonce. The peer MUST close this stream after sending the `DialAttempt` -message. +The peer dials the selected address, opens a stream with Protocol ID +`/libp2p/autonat/2/attempt` and sends a `DialAttempt` message with the nonce +received in the request. The peer MUST close this stream after sending the +`DialAttempt` message. Upon completion of the dial attempt, the peer sends a `DialResponse` message to -the initiator node on the `/libp2p/autonat/2.0.0/dial` stream with the index(0 -based) of the candidate that it attempted to dial and the appropriate -`ResponseStatus`. see [Requirements For -ResponseStatus](#requirements-for-responsestatus) +the initiator node on the `/libp2p/autonat/2/dial` stream. This response +contains a list of `DialStatus`es with a status for each address in the list up +to and including the address that the peer attempted to dial. The `DialStatus` +for an address is set according to [Requirements for +DialStatus](#requirements-for-dialstatus). The response also contains an +appropriate `ResponseStatus` set according to [Requirements For +ResponseStatus](#requirements-for-responsestatus). The initiator MUST check that the nonce received in the `DialAttempt` is the -same as the nonce the initiator sent in the `Candidate` for the candidate index -received in `DialResponse`. If the nonce is different, the initiator MUST -discard this response. +same as the nonce the it sent in the `DialRequest`. If the nonce is different, +it MUST discard this response. The peer MUST close the stream after sending the response. The initiator MUST close the stream after receiving the response. -### Requirements for ResponseStatus +### Requirements for DialStatus -On receiving a `DialRequest` the peer selects the first candidate on the list it -is capable of dialing. This candidate address is referred to as _addr_. The -`ResponseStatus` sent by the peer in the `DialResponse` message MUST be set -according to the following requirements +On receiving a `DialRequest` the peer goes through the list of addresses in the +request to select the first address that it is capable of dialing. For every +address that the peer checks, it assigns a `DialStatus` according to the +following requirements. + +For addresses that the peer decides to not dial: + +`E_ADDRESS_UNKNOWN`: The peer didn't understand the address. -`OK`: the peer was able to dial _addr_ successfully. +`E_TRANSPORT_NOT_SUPPORTED`: The peer understood the address, but has no +transport capable of dialing the requested address. -`E_DIAL_ERROR`: the peer attempted to dial _addr_ and was unable to connect. +`E_DIAL_REFUSED`: The peer didn't dial the address because of address +based restrictions like address based rate limit, the address being a private IP +address, or a relay address. -`E_DIAL_REFUSED`: the peer didn't attempt a dial because of rate limiting, -resource limit reached or blacklisting. +For the address that the peer decided to dial: -`E_TRANSPORT_NOT_SUPPORTED`: the peer didn't have the ability to dial any of the -requested addresses. +`E_DIAL_ERROR`: The peer was unable to connect to the address -`E_BAD_REQUEST`: the peer didn't attempt a dial because it was unable to decode -the message. +`E_CONN_UPGRADE_FAILED`: The peer was able to connect to the other address, but +failed to complete the connection upgrade step. + +`E_ATTEMPT_ERROR`: The peer was able to establish an upgraded connection but +some error occured when sending the nonce on the `/libp2p/autonat/2/attempt` +stream. + +`OK`: The peer successfully dialed the selected address. + +The peer MUST NOT send dial statuses for addresses after the one it selected to +dial. + + +### Requirements for ResponseStatus + +The `ResponseStatus` sent by the peer in the `DialResponse` message MUST be set +according to the following requirements + +`OK`: the peer completed the request successfully. A request is considered +completed successfully when the peer either completes a dial(successfully or +unsuccessfully) or rejects all addresses in the request as undialable. + +`E_BAD_REQUEST`: the peer was unable to decode the request. + +`E_REQUEST_REFUSED`: the peer didn't attempt to serve the request because of +rate limiting, resource limit reached or blacklisting. `E_INTERNAL_ERROR`: error not classified within the above error codes occured on peer that prevented it from completing the request. -Implementations MUST discard responses with status codes they do not understand +Implementations MUST discard responses with status codes they do not understand. ### Amplification Attack Prevention +![Interaction](autonat-v2-amplification-attack-prevention.svg) + + When a client asks a server to dial an address that is not the client's observed IP address, the server asks the client to send him some non trivial amount of bytes as a cost to dial a different IP address. To make amplification attacks @@ -130,8 +168,9 @@ than a new connection handshake cost. On receiving a `DialRequest`, the server selects the first address it is capable of dialing. If this selected address has a IP different from the client's -observed ip, the server sends a `DialDataRequest` message with `numBytes` set to -a sufficiently large value on the `/libp2p/autonat/2.0.0/dial-request` stream +observed IP, the server sends a `DialDataRequest` message with the selected +address's index(0 based) and `numBytes` set to a sufficiently large value on the +`/libp2p/autonat/2/dial` stream Upon receiving a `DialDataRequest` message, the client decides whether to accept or reject the cost of dial. If the client rejects the cost, the client resets @@ -151,71 +190,65 @@ unreachable if more than 3 peers report unsuccessful dials. Implementations are free to use different heuristics than this one -Implementations SHOULD only verify reachability for private addresses as defined -in [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) with peers that are -on the same subnet - - ## RPC Messages All RPC messages sent over a stream are prefixed with the message length in bytes, encoded as an unsigned variable length integer as defined by the [multiformats unsigned-varint spec][uvarint-spec]. -All RPC messages on stream `/libp2p/autonat/2.0.0/dial` are of type -`DialMessage`. A `DialRequest` message is sent as a `DialMessage` with the -`dialRequest` field set to `DialRequest` message. `DialResponse` and -`DialDataRequest` are handled similarly. On stream -`/libp2p/autonat/2.0.0/attempt`, there is a single message type `AttemptMessage` -where `DialAttempt` is handled similarly. +All RPC messages on stream `/libp2p/autonat/2/dial` are of type `Message`. A +`DialRequest` message is sent as a `Message` with the `msg` field set to +`DialRequest`. `DialResponse` and `DialDataRequest` are handled similarly. + +On stream `/libp2p/autonat/2/attempt`, a `DialAttempt` message is sent directly ```proto syntax = "proto3"; -message DialMessage { - oneof msg { - DialRequest dialRequest = 1; - DialResponse dialResponse = 2; - DialDataRequest dialDataRequest = 3; - } -} - -message Candidate { - bytes addr = 1; - fixed64 nonce = 2; +message Message { + oneof msg { + DialRequest dialRequest = 1; + DialResponse dialResponse = 2; + DialDataRequest dialDataRequest = 3; + } } message DialRequest { - repeated Candidate candidates = 1; + repeated bytes addrs = 1; + fixed64 nonce = 2; } message DialDataRequest { - uint64 numBytes = 1; + uint32 addrIdx = 1; + uint64 numBytes = 2; } message DialResponse { - enum ResponseStatus { - OK = 0; - E_DIAL_ERROR = 100; - E_DIAL_REFUSED = 101; - E_TRANSPORT_NOT_SUPPORTED = 102; - E_BAD_REQUEST = 200; - E_INTERNAL_ERROR = 300; - } - - ResponseStatus status = 1; - int32 addrIdx = 2; -} - -message AttemptMessage { - oneof msg { - DialAttempt dialAttempt = 1; - } + enum ResponseStatus { + OK = 0; + E_BAD_REQUEST = 100 + E_REQUEST_REFUSED = 101; + E_INTERNAL_ERROR = 300; + } + + enum DialStatus { + OK = 0; + E_DIAL_ERROR = 100; + E_CONN_UPGRADE_FAILED = 101; + E_ATTEMPT_ERROR = 102; + E_DIAL_REFUSED = 200; + E_TRANSPORT_NOT_SUPPORTED = 300; + E_ADDRESS_UNKNOWN = 301; + } + + ResponseStatus status = 1; + repeated DialStatus dialStatuses = 2; } message DialAttempt { fixed64 nonce = 1; } + ``` [uvarint-spec]: https://github.com/multiformats/unsigned-varint diff --git a/autonat/autonat-v2.plantuml b/autonat/autonat-v2.plantuml index 0722fc89e..2247bebe9 100644 --- a/autonat/autonat-v2.plantuml +++ b/autonat/autonat-v2.plantuml @@ -2,18 +2,19 @@ participant Cli participant Srv -skinparam backgroundColor white skinparam sequenceMessageAlign center +skinparam defaultFontName monospaced + == Dial Request Success== -Cli -> Srv: [dial] DialRequest: (Addr1, Token1), (Addr2, Token2) -Srv -> Cli: [attempt] DialAttempt: Token2 -Srv -> Cli: [dial] DialResponse: STATUS: OK, CandidateIdx: 1 +Cli -> Srv: [dial] DialRequest:{nonce: 0xabcd, addrs: (addr1, addr2, addr3)} +Srv -> Cli: [attempt]addr2 DialAttempt:{nonce: 0xabcd} +Srv -> Cli: [dial] DialResponse:{status: OK, dialStatuses:(E_TRANSPORT_NOT_SUPPORTED, OK)} == Dial Request Failure== -Cli -> Srv: [dial] DialRequest: (Addr1, Token1), (Addr2, Token2) -Srv ->x Cli: [attempt] DialAttempt: Token2 -Srv -> Cli: [dial] DialResponse: STATUS: E_DIAL_ERROR, CandidateIdx: 1 -@enduml +Cli -> Srv: [dial] DialRequest:{nonce: 0xabcd, addrs: (addr1, addr2, addr3)} +Srv ->x Cli: [attempt]addr2 DialAttempt:{nonce: 0xabcd} +Srv -> Cli: [dial] DialResponse:{status: OK, dialStatuses:(E_TRANSPORT_NOT_SUPPORTED, E_DIAL_ERROR)} +@enduml \ No newline at end of file diff --git a/autonat/autonat-v2.svg b/autonat/autonat-v2.svg index c0df08821..adf10c680 100644 --- a/autonat/autonat-v2.svg +++ b/autonat/autonat-v2.svg @@ -1 +1 @@ -CliCliSrvSrvDial Request Success[dial] DialRequest: (Addr1, Token1), (Addr2, Token2)[attempt] DialAttempt: Token2[dial] DialResponse: STATUS: OK, CandidateIdx: 1Dial Request Failure[dial] DialRequest: (Addr1, Token1), (Addr2, Token2)[attempt] DialAttempt: Token2[dial] DialResponse: STATUS: E_DIAL_ERROR, CandidateIdx: 1 \ No newline at end of file +CliCliSrvSrvDial Request Success[dial] DialRequest:{nonce: 0xabcd, addrs: (addr1, addr2, addr3)}[attempt]addr2 DialAttempt:{nonce: 0xabcd}[dial] DialResponse:{status: OK, dialStatuses:(E_TRANSPORT_NOT_SUPPORTED, OK)}Dial Request Failure[dial] DialRequest:{nonce: 0xabcd, addrs: (addr1, addr2, addr3)}[attempt]addr2 DialAttempt:{nonce: 0xabcd}[dial] DialResponse:{status: OK, dialStatuses:(E_TRANSPORT_NOT_SUPPORTED, E_DIAL_ERROR)} \ No newline at end of file From f28511c52cc7078fd9a6b4b399b068212ba821f4 Mon Sep 17 00:00:00 2001 From: sukun Date: Tue, 11 Jul 2023 20:05:24 +0530 Subject: [PATCH 10/25] drop terms node and peer in favour of client and server --- autonat/autonat-v2.md | 94 +++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 205403daa..548a74a76 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -62,96 +62,96 @@ attacks. `autonat v1` disallowed such dials to prevent amplification attacks. ![Autonat V2 Interaction](autonat-v2.svg) -A node wishing to determine reachability of its adddresses sends a `DialRequest` -message to a peer on a stream with protocol ID `/libp2p/autonat/2/dial`. Each -`DialRequest` is sent on a new stream. +A client node wishing to determine reachability of its adddresses sends a +`DialRequest` message to a server on a stream with protocol ID +`/libp2p/autonat/2/dial`. Each `DialRequest` is sent on a new stream. This `DialRequest` message has a list of addresses and a fixed64 `nonce`. The list is ordered in descending order of priority for verification. -Upon receiving this request, the peer selects the first address from the list of -addresses that it is capable of dialing. The peer MUST NOT dial any address +Upon receiving this request, the server selects the first address from the list +of addresses that it is capable of dialing. The server MUST NOT dial any address other than this one. If this selected address has an IP address different from -the requesting node's observed IP address, peer initiates the Amplification +the requesting node's observed IP address, server initiates the Amplification attack prevention mechanism (see [Amplification Attack -Prevention](#amplification-attack-prevention) ). On completion, the peer +Prevention](#amplification-attack-prevention) ). On completion, the server proceeds to the next step. If the selected address has the same IP address as -the requesting node's observed IP address, peer proceeds to the next step -skipping Amplification Attack prevention steps. +the client's observed IP address, server proceeds to the next step skipping +Amplification Attack Prevention steps. -The peer dials the selected address, opens a stream with Protocol ID +The server dials the selected address, opens a stream with Protocol ID `/libp2p/autonat/2/attempt` and sends a `DialAttempt` message with the nonce -received in the request. The peer MUST close this stream after sending the +received in the request. The server MUST close this stream after sending the `DialAttempt` message. -Upon completion of the dial attempt, the peer sends a `DialResponse` message to -the initiator node on the `/libp2p/autonat/2/dial` stream. This response -contains a list of `DialStatus`es with a status for each address in the list up -to and including the address that the peer attempted to dial. The `DialStatus` -for an address is set according to [Requirements for +Upon completion of the dial attempt, the server sends a `DialResponse` message +to the client node on the `/libp2p/autonat/2/dial` stream. The response contains +a list of `DialStatus`es with a status for each address in the list up to and +including the address that the server attempted to dial. The `DialStatus` for an +address is set according to [Requirements for DialStatus](#requirements-for-dialstatus). The response also contains an appropriate `ResponseStatus` set according to [Requirements For ResponseStatus](#requirements-for-responsestatus). -The initiator MUST check that the nonce received in the `DialAttempt` is the -same as the nonce the it sent in the `DialRequest`. If the nonce is different, -it MUST discard this response. +The client MUST check that the nonce received in the `DialAttempt` is the same +as the nonce the it sent in the `DialRequest`. If the nonce is different, it +MUST discard this response. -The peer MUST close the stream after sending the response. The initiator MUST +The server MUST close the stream after sending the response. The client MUST close the stream after receiving the response. ### Requirements for DialStatus -On receiving a `DialRequest` the peer goes through the list of addresses in the -request to select the first address that it is capable of dialing. For every -address that the peer checks, it assigns a `DialStatus` according to the +On receiving a `DialRequest` the server goes through the list of addresses in +the request to select the first address that it is capable of dialing. For every +address that the server checks, it assigns a `DialStatus` according to the following requirements. -For addresses that the peer decides to not dial: +For addresses that the server decides to not dial: -`E_ADDRESS_UNKNOWN`: The peer didn't understand the address. +`E_ADDRESS_UNKNOWN`: The server didn't understand the address. -`E_TRANSPORT_NOT_SUPPORTED`: The peer understood the address, but has no +`E_TRANSPORT_NOT_SUPPORTED`: The server understood the address, but has no transport capable of dialing the requested address. -`E_DIAL_REFUSED`: The peer didn't dial the address because of address -based restrictions like address based rate limit, the address being a private IP +`E_DIAL_REFUSED`: The server didn't dial the address because of address based +restrictions like address based rate limit, the address being a private IP address, or a relay address. -For the address that the peer decided to dial: +For the address that the server decided to dial: -`E_DIAL_ERROR`: The peer was unable to connect to the address +`E_DIAL_ERROR`: The server was unable to connect to the address -`E_CONN_UPGRADE_FAILED`: The peer was able to connect to the other address, but -failed to complete the connection upgrade step. +`E_CONN_UPGRADE_FAILED`: The server was able to connect to the address, but +failed to complete the connection upgrade step. -`E_ATTEMPT_ERROR`: The peer was able to establish an upgraded connection but +`E_ATTEMPT_ERROR`: The server was able to establish an upgraded connection but some error occured when sending the nonce on the `/libp2p/autonat/2/attempt` stream. -`OK`: The peer successfully dialed the selected address. +`OK`: The server successfully dialed the selected address. -The peer MUST NOT send dial statuses for addresses after the one it selected to -dial. +The server MUST NOT send dial statuses for addresses after the one it selected +to dial. ### Requirements for ResponseStatus -The `ResponseStatus` sent by the peer in the `DialResponse` message MUST be set -according to the following requirements +The `ResponseStatus` sent by the server in the `DialResponse` message MUST be +set according to the following requirements -`OK`: the peer completed the request successfully. A request is considered -completed successfully when the peer either completes a dial(successfully or +`OK`: the server completed the request successfully. A request is considered +completed successfully when the server either completes a dial(successfully or unsuccessfully) or rejects all addresses in the request as undialable. -`E_BAD_REQUEST`: the peer was unable to decode the request. +`E_BAD_REQUEST`: the server was unable to decode the request. -`E_REQUEST_REFUSED`: the peer didn't attempt to serve the request because of +`E_REQUEST_REFUSED`: the server didn't attempt to serve the request because of rate limiting, resource limit reached or blacklisting. `E_INTERNAL_ERROR`: error not classified within the above error codes occured on -peer that prevented it from completing the request. +server that prevented it from completing the request. Implementations MUST discard responses with status codes they do not understand. @@ -180,13 +180,13 @@ server on receiving `numBytes` bytes proceeds to dial the candidate address. ## Implementation Suggestions -For any given address, implementations SHOULD do the following +For any given address, client implementations SHOULD do the following - periodically recheck reachability status -- query multiple peers to determine reachability +- query multiple servers to determine reachability The suggested heuristic for implementations is to consider an address reachable -if more than 3 peers report a successful dial and to consider an address -unreachable if more than 3 peers report unsuccessful dials. +if more than 3 servers report a successful dial and to consider an address +unreachable if more than 3 servers report unsuccessful dials. Implementations are free to use different heuristics than this one From d4da279d631b4d552adeb23fbbc0b9f11b8bb1ce Mon Sep 17 00:00:00 2001 From: sukun Date: Tue, 11 Jul 2023 20:17:47 +0530 Subject: [PATCH 11/25] client should not reuse listen port while making dial request --- autonat/autonat-v2.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 548a74a76..e5fdf1c5e 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -181,8 +181,17 @@ server on receiving `numBytes` bytes proceeds to dial the candidate address. ## Implementation Suggestions For any given address, client implementations SHOULD do the following -- periodically recheck reachability status -- query multiple servers to determine reachability +- Periodically recheck reachability status. +- Query multiple servers to determine reachability. +- Clients SHOULD NOT reuse their listening port when making a `DialRequest`. If +the client reuses its listening port while making the request and the server +reuses its listening port while making the dial attempt, the server will end up +trying to establish a connection on the same 4 tuple that the client is already +connected on. Moreover, if the client is behind an Address-Dependent NAT as +defined in [RFC +4787](https://datatracker.ietf.org/doc/html/rfc4787#section-4.1), reusing the +listening port for making the request will create a NAT mapping that's reachable +only from the server. The suggested heuristic for implementations is to consider an address reachable if more than 3 servers report a successful dial and to consider an address From 5b8d37dbbfa4092e369c5306273055665abfd4c5 Mon Sep 17 00:00:00 2001 From: sukun Date: Tue, 11 Jul 2023 20:43:34 +0530 Subject: [PATCH 12/25] explicitly disallow probing for private addresses --- autonat/autonat-v2.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index e5fdf1c5e..7606017e9 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -57,7 +57,6 @@ from the requesting node's observed IP address without risking amplification attacks. `autonat v1` disallowed such dials to prevent amplification attacks. - ## AutoNAT V2 Protocol ![Autonat V2 Interaction](autonat-v2.svg) @@ -67,7 +66,10 @@ A client node wishing to determine reachability of its adddresses sends a `/libp2p/autonat/2/dial`. Each `DialRequest` is sent on a new stream. This `DialRequest` message has a list of addresses and a fixed64 `nonce`. The -list is ordered in descending order of priority for verification. +list is ordered in descending order of priority for verification. AutoNAT V2 is +only for testing reachability on Public Internet. Client MUST NOT send any +private address as defined in [RFC +1918](https://datatracker.ietf.org/doc/html/rfc1918#section-3) in the list. Upon receiving this request, the server selects the first address from the list of addresses that it is capable of dialing. The server MUST NOT dial any address @@ -94,7 +96,7 @@ appropriate `ResponseStatus` set according to [Requirements For ResponseStatus](#requirements-for-responsestatus). The client MUST check that the nonce received in the `DialAttempt` is the same -as the nonce the it sent in the `DialRequest`. If the nonce is different, it +as the nonce it sent in the `DialRequest`. If the nonce is different, it MUST discard this response. The server MUST close the stream after sending the response. The client MUST From 05a0de2ec50ea0bfe4d81a032d7749064d19a00c Mon Sep 17 00:00:00 2001 From: sukun Date: Wed, 12 Jul 2023 19:44:08 +0530 Subject: [PATCH 13/25] add explanation for amplification attack prevention mechanism --- autonat/autonat-v2.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 7606017e9..17d823f7b 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -96,8 +96,8 @@ appropriate `ResponseStatus` set according to [Requirements For ResponseStatus](#requirements-for-responsestatus). The client MUST check that the nonce received in the `DialAttempt` is the same -as the nonce it sent in the `DialRequest`. If the nonce is different, it -MUST discard this response. +as the nonce it sent in the `DialRequest`. If the nonce is different, it MUST +discard this response. The server MUST close the stream after sending the response. The client MUST close the stream after receiving the response. @@ -161,7 +161,6 @@ Implementations MUST discard responses with status codes they do not understand. ![Interaction](autonat-v2-amplification-attack-prevention.svg) - When a client asks a server to dial an address that is not the client's observed IP address, the server asks the client to send him some non trivial amount of bytes as a cost to dial a different IP address. To make amplification attacks @@ -180,6 +179,16 @@ the stream and the `DialRequest` is considered aborted. If the client accepts the cost, the client starts transferring `numBytes` bytes to the server. The server on receiving `numBytes` bytes proceeds to dial the candidate address. +If an attacker asks a server to dial a victim node, the only benefit that the +attacker gets is forcing the server and the victim to do a cryptographic +handshake which costs some bandwidth and compute. The attacker by itself can do +a lot of handshakes with the victim without spending any compute by using the +same key repeatedly. The only benefit of going via the server to do this attack +is not spending bandwidth required for a handshake. So the prevention mechanism +only focuses on bandwidth costs. There is a minor benefit of bypassing IP +blocklists, but that's made unattractive by the fact that servers may ask 5x +more data than the bandwidth cost of a handshake. + ## Implementation Suggestions For any given address, client implementations SHOULD do the following From 8b526430f3b6551022977aada203340b186e96ac Mon Sep 17 00:00:00 2001 From: sukun Date: Sun, 16 Jul 2023 17:57:09 +0530 Subject: [PATCH 14/25] add recommendation for 30k - 100k bytes --- autonat/autonat-v2.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 17d823f7b..60e49d2c8 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -162,10 +162,11 @@ Implementations MUST discard responses with status codes they do not understand. ![Interaction](autonat-v2-amplification-attack-prevention.svg) When a client asks a server to dial an address that is not the client's observed -IP address, the server asks the client to send him some non trivial amount of -bytes as a cost to dial a different IP address. To make amplification attacks -unattractive, the number of bytes is decided such that it's sufficiently larger -than a new connection handshake cost. +IP address, the server asks the client to send some non trivial amount of bytes +as a cost to dial a different IP address. To make amplification attacks +unattractive, servers SHOULD ask for 30k to 100k bytes. Since most handshakes +cost less than 10k bytes in bandwidth, 30kB is sufficient to make attacks +unattractive. On receiving a `DialRequest`, the server selects the first address it is capable of dialing. If this selected address has a IP different from the client's @@ -177,7 +178,7 @@ Upon receiving a `DialDataRequest` message, the client decides whether to accept or reject the cost of dial. If the client rejects the cost, the client resets the stream and the `DialRequest` is considered aborted. If the client accepts the cost, the client starts transferring `numBytes` bytes to the server. The -server on receiving `numBytes` bytes proceeds to dial the candidate address. +server on receiving `numBytes` bytes proceeds to dial the candidate address. If an attacker asks a server to dial a victim node, the only benefit that the attacker gets is forcing the server and the victim to do a cryptographic @@ -246,7 +247,7 @@ message DialDataRequest { message DialResponse { enum ResponseStatus { OK = 0; - E_BAD_REQUEST = 100 + E_BAD_REQUEST = 100; E_REQUEST_REFUSED = 101; E_INTERNAL_ERROR = 300; } From dd2750c2be624d79e9962367e6fcb2229f8c5721 Mon Sep 17 00:00:00 2001 From: sukun Date: Sat, 12 Aug 2023 22:42:11 +0530 Subject: [PATCH 15/25] move DialStatus proto out of Response --- autonat/autonat-v2.md | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 60e49d2c8..da580a93b 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -231,6 +231,7 @@ message Message { DialRequest dialRequest = 1; DialResponse dialResponse = 2; DialDataRequest dialDataRequest = 3; + DialDataResponse dialDataResponse = 4; } } @@ -239,37 +240,46 @@ message DialRequest { fixed64 nonce = 2; } + message DialDataRequest { uint32 addrIdx = 1; uint64 numBytes = 2; } + +enum DialStatus { + OK = 0; + E_DIAL_ERROR = 100; + E_CONN_UPGRADE_FAILED = 101; + E_ATTEMPT_ERROR = 102; + E_DIAL_REFUSED = 200; + E_TRANSPORT_NOT_SUPPORTED = 300; + E_ADDRESS_UNKNOWN = 301; + SKIPPED = 400; +} + + message DialResponse { enum ResponseStatus { - OK = 0; + ResponseStatus_OK = 0; E_BAD_REQUEST = 100; E_REQUEST_REFUSED = 101; E_INTERNAL_ERROR = 300; } - enum DialStatus { - OK = 0; - E_DIAL_ERROR = 100; - E_CONN_UPGRADE_FAILED = 101; - E_ATTEMPT_ERROR = 102; - E_DIAL_REFUSED = 200; - E_TRANSPORT_NOT_SUPPORTED = 300; - E_ADDRESS_UNKNOWN = 301; - } - ResponseStatus status = 1; repeated DialStatus dialStatuses = 2; } + +message DialDataResponse { + bytes data = 1; +} + + message DialAttempt { fixed64 nonce = 1; } - ``` [uvarint-spec]: https://github.com/multiformats/unsigned-varint From 4e6ecaaddbb4c8b4c60a7c2dc4a94cd3f5d78bf1 Mon Sep 17 00:00:00 2001 From: sukun Date: Sat, 12 Aug 2023 22:53:36 +0530 Subject: [PATCH 16/25] wrap data sent for amplification attack prevention in a protobuf --- autonat/autonat-v2.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index da580a93b..917024eb0 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -178,7 +178,13 @@ Upon receiving a `DialDataRequest` message, the client decides whether to accept or reject the cost of dial. If the client rejects the cost, the client resets the stream and the `DialRequest` is considered aborted. If the client accepts the cost, the client starts transferring `numBytes` bytes to the server. The -server on receiving `numBytes` bytes proceeds to dial the candidate address. +client transfers these bytes wrapped in `DialDataResponse` protobufs where the +`data` field in each individual protobuf is limited to 4096 bytes in length. +This allows implementations to use a small buffer for reading and sending the +data as well as cleaner interoperability with rest of the messages exchanged on +the stream. The server only calculates the size of the `data` field of +`DialDataResponse` protobufs as the bytes transferred and not the total size of +the protobuf. The server on receiving numBytes bytes proceeds to dial the selected address. If an attacker asks a server to dial a victim node, the only benefit that the attacker gets is forcing the server and the victim to do a cryptographic @@ -195,21 +201,16 @@ more data than the bandwidth cost of a handshake. For any given address, client implementations SHOULD do the following - Periodically recheck reachability status. - Query multiple servers to determine reachability. -- Clients SHOULD NOT reuse their listening port when making a `DialRequest`. If -the client reuses its listening port while making the request and the server -reuses its listening port while making the dial attempt, the server will end up -trying to establish a connection on the same 4 tuple that the client is already -connected on. Moreover, if the client is behind an Address-Dependent NAT as -defined in [RFC -4787](https://datatracker.ietf.org/doc/html/rfc4787#section-4.1), reusing the -listening port for making the request will create a NAT mapping that's reachable -only from the server. The suggested heuristic for implementations is to consider an address reachable if more than 3 servers report a successful dial and to consider an address -unreachable if more than 3 servers report unsuccessful dials. +unreachable if more than 3 servers report unsuccessful dials. Implementations +are free to use different heuristics than this one + +Servers SHOULD NOT reuse their listening port when making a dial attempt. In +case the client has reused their listen port when dialing out to the server, not +reusing the listen port for attempts prevents accidental hole punches. -Implementations are free to use different heuristics than this one ## RPC Messages From 2af33091a34bcae26566fe18b4549271e1ec396c Mon Sep 17 00:00:00 2001 From: sukun Date: Wed, 16 Aug 2023 17:45:45 +0530 Subject: [PATCH 17/25] send only a single dial status --- autonat/autonat-v2.md | 126 ++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 72 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 917024eb0..07eb208e9 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -43,7 +43,7 @@ Initially, such a transport will not be widely supported in the network. Requests for verifying such addresses can be reused to get information about other addresses -The client can verify that the server did successfully dial an address of the +The client can verify the server did successfully dial an address of the same transport as it reported in the response by checking the local address of the connection on which the nonce was received on. @@ -61,25 +61,25 @@ attacks. `autonat v1` disallowed such dials to prevent amplification attacks. ![Autonat V2 Interaction](autonat-v2.svg) -A client node wishing to determine reachability of its adddresses sends a +A client node wishing to determine reachability of its addresses sends a `DialRequest` message to a server on a stream with protocol ID `/libp2p/autonat/2/dial`. Each `DialRequest` is sent on a new stream. This `DialRequest` message has a list of addresses and a fixed64 `nonce`. The list is ordered in descending order of priority for verification. AutoNAT V2 is -only for testing reachability on Public Internet. Client MUST NOT send any +only for testing reachability on Public Internet. Client SHOULD NOT send any private address as defined in [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918#section-3) in the list. -Upon receiving this request, the server selects the first address from the list -of addresses that it is capable of dialing. The server MUST NOT dial any address -other than this one. If this selected address has an IP address different from -the requesting node's observed IP address, server initiates the Amplification -attack prevention mechanism (see [Amplification Attack -Prevention](#amplification-attack-prevention) ). On completion, the server -proceeds to the next step. If the selected address has the same IP address as -the client's observed IP address, server proceeds to the next step skipping -Amplification Attack Prevention steps. +Upon receiving this request, the server selects an address from the list to +dial. The server SHOULD use the first address it is willing to dial. The +server MUST NOT dial any address other than this one. If this selected address +has an IP address different from the requesting node's observed IP address, +server initiates the Amplification attack prevention mechanism (see +[Amplification Attack Prevention](#amplification-attack-prevention) ). On +completion, the server proceeds to the next step. If the selected address has +the same IP address as the client's observed IP address, server proceeds to the +next step skipping Amplification Attack Prevention steps. The server dials the selected address, opens a stream with Protocol ID `/libp2p/autonat/2/attempt` and sends a `DialAttempt` message with the nonce @@ -88,9 +88,9 @@ received in the request. The server MUST close this stream after sending the Upon completion of the dial attempt, the server sends a `DialResponse` message to the client node on the `/libp2p/autonat/2/dial` stream. The response contains -a list of `DialStatus`es with a status for each address in the list up to and -including the address that the server attempted to dial. The `DialStatus` for an -address is set according to [Requirements for +`addrIdx`, the index of the address the server selected to dial and +`DialStatus`, a dial status indicating the outcome of the dial attempt. The +`DialStatus` for an address is set according to [Requirements for DialStatus](#requirements-for-dialstatus). The response also contains an appropriate `ResponseStatus` set according to [Requirements For ResponseStatus](#requirements-for-responsestatus). @@ -105,55 +105,43 @@ close the stream after receiving the response. ### Requirements for DialStatus -On receiving a `DialRequest` the server goes through the list of addresses in -the request to select the first address that it is capable of dialing. For every -address that the server checks, it assigns a `DialStatus` according to the -following requirements. +On receiving a `DialRequest`, the server first selects an address that it will dial. -For addresses that the server decides to not dial: +If server chooses to not dial any of the requested addresses, `DialStatus` is +set to `E_DIAL_REFUSED`. In this case no dial attempt was made and +the `addrIdx` sent with the response is meaningless. -`E_ADDRESS_UNKNOWN`: The server didn't understand the address. +If the server selects an address for dialing, `addrIdx` is set to the +index(zero-based) of the address on the list and the `DialStatus` is +set according to the following consideration: -`E_TRANSPORT_NOT_SUPPORTED`: The server understood the address, but has no -transport capable of dialing the requested address. +If the server was unable to connect to the client on the selected address, +`DialStatus` is set to `E_DIAL_ERROR`, indicating the selected address is +not publicly reachable. -`E_DIAL_REFUSED`: The server didn't dial the address because of address based -restrictions like address based rate limit, the address being a private IP -address, or a relay address. - -For the address that the server decided to dial: - -`E_DIAL_ERROR`: The server was unable to connect to the address - -`E_CONN_UPGRADE_FAILED`: The server was able to connect to the address, but -failed to complete the connection upgrade step. - -`E_ATTEMPT_ERROR`: The server was able to establish an upgraded connection but -some error occured when sending the nonce on the `/libp2p/autonat/2/attempt` -stream. - -`OK`: The server successfully dialed the selected address. - -The server MUST NOT send dial statuses for addresses after the one it selected -to dial. +If the server was able to connect to the client on the selected address, but an +error occured while sending an nonce on the `/libp2p/autonat/2/attempt` stream, +`DialStatus` is set to `E_DIAL_ATTEMPT_ERROR`. This might happen in case of +resource limited situations on client or server, or when either the client or +the server is misconfigured. +If the server was able to connect to the client and successfully send a nonce on +the `/libp2p/autonat/2/attempt` stream, `DialStatus` is set to `OK`. ### Requirements for ResponseStatus The `ResponseStatus` sent by the server in the `DialResponse` message MUST be set according to the following requirements -`OK`: the server completed the request successfully. A request is considered -completed successfully when the server either completes a dial(successfully or -unsuccessfully) or rejects all addresses in the request as undialable. - -`E_BAD_REQUEST`: the server was unable to decode the request. - -`E_REQUEST_REFUSED`: the server didn't attempt to serve the request because of +`E_REQUEST_REJECTED`: the server didn't attempt to serve the request because of rate limiting, resource limit reached or blacklisting. `E_INTERNAL_ERROR`: error not classified within the above error codes occured on -server that prevented it from completing the request. +server preventing it from completing the request. + +`OK`: the server completed the request successfully. A request is considered +completed successfully when the server either completes a dial(successfully or +unsuccessfully) or rejects all addresses in the request as undialable. Implementations MUST discard responses with status codes they do not understand. @@ -171,8 +159,8 @@ unattractive. On receiving a `DialRequest`, the server selects the first address it is capable of dialing. If this selected address has a IP different from the client's observed IP, the server sends a `DialDataRequest` message with the selected -address's index(0 based) and `numBytes` set to a sufficiently large value on the -`/libp2p/autonat/2/dial` stream +address's index(zero-based) and `numBytes` set to a sufficiently large value on +the `/libp2p/autonat/2/dial` stream Upon receiving a `DialDataRequest` message, the client decides whether to accept or reject the cost of dial. If the client rejects the cost, the client resets @@ -181,12 +169,11 @@ the cost, the client starts transferring `numBytes` bytes to the server. The client transfers these bytes wrapped in `DialDataResponse` protobufs where the `data` field in each individual protobuf is limited to 4096 bytes in length. This allows implementations to use a small buffer for reading and sending the -data as well as cleaner interoperability with rest of the messages exchanged on -the stream. The server only calculates the size of the `data` field of -`DialDataResponse` protobufs as the bytes transferred and not the total size of -the protobuf. The server on receiving numBytes bytes proceeds to dial the selected address. +data. Only the size of the `data` field of `DialDataResponse` protobufs is +counted towards the bytes transferred. Once the server has received numBytes +bytes, it proceeds to dial the selected address. -If an attacker asks a server to dial a victim node, the only benefit that the +If an attacker asks a server to dial a victim node, the only benefit the attacker gets is forcing the server and the victim to do a cryptographic handshake which costs some bandwidth and compute. The attacker by itself can do a lot of handshakes with the victim without spending any compute by using the @@ -224,8 +211,7 @@ All RPC messages on stream `/libp2p/autonat/2/dial` are of type `Message`. A On stream `/libp2p/autonat/2/attempt`, a `DialAttempt` message is sent directly -```proto -syntax = "proto3"; +```proto3 message Message { oneof msg { @@ -249,27 +235,23 @@ message DialDataRequest { enum DialStatus { - OK = 0; - E_DIAL_ERROR = 100; - E_CONN_UPGRADE_FAILED = 101; - E_ATTEMPT_ERROR = 102; - E_DIAL_REFUSED = 200; - E_TRANSPORT_NOT_SUPPORTED = 300; - E_ADDRESS_UNKNOWN = 301; - SKIPPED = 400; + E_DIAL_REFUSED = 0; + E_DIAL_ERROR = 100; + E_ATTEMPT_ERROR = 101; + OK = 200; } message DialResponse { enum ResponseStatus { - ResponseStatus_OK = 0; - E_BAD_REQUEST = 100; - E_REQUEST_REFUSED = 101; - E_INTERNAL_ERROR = 300; + E_INTERNAL_ERROR = 0; + E_REQUEST_REJECTED = 100; + ResponseStatus_OK = 200; } ResponseStatus status = 1; - repeated DialStatus dialStatuses = 2; + uint32 addrIdx = 2; + DialStatus dialStatus = 3; } From 6b1604b207d7675b4e3df7293834f75e0fa8c0d7 Mon Sep 17 00:00:00 2001 From: sukun Date: Fri, 18 Aug 2023 12:01:59 +0530 Subject: [PATCH 18/25] add comment regarding nonce --- autonat/autonat-v2.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 07eb208e9..477a469e6 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -196,7 +196,9 @@ are free to use different heuristics than this one Servers SHOULD NOT reuse their listening port when making a dial attempt. In case the client has reused their listen port when dialing out to the server, not -reusing the listen port for attempts prevents accidental hole punches. +reusing the listen port for attempts prevents accidental hole punches. Clients +SHOULD only rely on the nonce and not on the peerID for tallying the dial +attempt as the server is free to use a separate peerID for the dial attempts. ## RPC Messages @@ -222,6 +224,7 @@ message Message { } } + message DialRequest { repeated bytes addrs = 1; fixed64 nonce = 2; @@ -235,10 +238,12 @@ message DialDataRequest { enum DialStatus { - E_DIAL_REFUSED = 0; - E_DIAL_ERROR = 100; - E_ATTEMPT_ERROR = 101; - OK = 200; + // Default value to force servers to explicitly set the status + E_INTERNAL_ERROR = 0; + E_DIAL_REFUSED = 100; + E_DIAL_ERROR = 101; + E_ATTEMPT_ERROR = 102; + OK = 200; } From f979fac4f3d3d83dbc6a7e3108addc445b3fdf4b Mon Sep 17 00:00:00 2001 From: sukun Date: Mon, 21 Aug 2023 01:05:17 +0530 Subject: [PATCH 19/25] rename attempt to dial-back --- autonat/autonat-v2.md | 119 ++++++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 477a469e6..73b6ff4bb 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -18,12 +18,13 @@ Interest Group: [@marten-seemann], [@marcopolo], [@mxinden] ## Overview A priori, a node cannot know if it is behind a NAT / firewall or if it is -publicly reachable. Knowing its NAT status is essential for the node to be -well-behaved in the network: A node that's behind a NAT / firewall doesn't need -to advertise its (undialable) addresses to the rest of the network, preventing -superfluous dials from other peers. Furthermore, it might actively seek to -improve its connectivity by finding a relay server, which would allow other -peers to establish a relayed connection. +publicly reachable. Moreover, the node may be publicly reachable on some of its +addresses and not on others. Knowing reachability for its addresses is essential +for the node to be well-behaved in the network: A node doesn't need to advertise +its unreachable addresses to the rest of the network, preventing superfluous +dials from other peers. Furthermore, in case it has no publicly reachable +addresses, it might actively seek to improve its connectivity by finding a relay +server, which would allow other peers to establish a relayed connection. In `autonat v2` client sends a request with a priority ordered list of addresses and a nonce. On receiving this request the server dials the first address in the @@ -43,9 +44,9 @@ Initially, such a transport will not be widely supported in the network. Requests for verifying such addresses can be reused to get information about other addresses -The client can verify the server did successfully dial an address of the -same transport as it reported in the response by checking the local address of -the connection on which the nonce was received on. +The client can verify the server did successfully dial an address of the same +transport as it reported in the response by checking the local address of the +connection on which the nonce was received on. Compared to `autonat v1` there are three major differences 1. `autonat v1` allowed testing reachability for the node. `autonat v2` allows @@ -72,31 +73,31 @@ private address as defined in [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918#section-3) in the list. Upon receiving this request, the server selects an address from the list to -dial. The server SHOULD use the first address it is willing to dial. The -server MUST NOT dial any address other than this one. If this selected address -has an IP address different from the requesting node's observed IP address, -server initiates the Amplification attack prevention mechanism (see -[Amplification Attack Prevention](#amplification-attack-prevention) ). On -completion, the server proceeds to the next step. If the selected address has -the same IP address as the client's observed IP address, server proceeds to the -next step skipping Amplification Attack Prevention steps. +dial. The server SHOULD use the first address it is willing to dial. The server +MUST NOT dial any address other than this one. If this selected address has an +IP address different from the requesting node's observed IP address, server +initiates the Amplification attack prevention mechanism (see [Amplification +Attack Prevention](#amplification-attack-prevention) ). On completion, the +server proceeds to the next step. If the selected address has the same IP +address as the client's observed IP address, server proceeds to the next step +skipping Amplification Attack Prevention steps. The server dials the selected address, opens a stream with Protocol ID -`/libp2p/autonat/2/attempt` and sends a `DialAttempt` message with the nonce -received in the request. The server MUST close this stream after sending the -`DialAttempt` message. - -Upon completion of the dial attempt, the server sends a `DialResponse` message -to the client node on the `/libp2p/autonat/2/dial` stream. The response contains -`addrIdx`, the index of the address the server selected to dial and -`DialStatus`, a dial status indicating the outcome of the dial attempt. The +`/libp2p/autonat/2/dial-back` and sends a `DialBack` message with the nonce +received in the request. The client MUST close this stream after receiving the +`DialBack` message. + +Upon completion of the dial back, the server sends a `DialResponse` message to +the client node on the `/libp2p/autonat/2/dial-request` stream. The response +contains `addrIdx`, the index of the address the server selected to dial and +`DialStatus`, a dial status indicating the outcome of the dial back. The `DialStatus` for an address is set according to [Requirements for DialStatus](#requirements-for-dialstatus). The response also contains an appropriate `ResponseStatus` set according to [Requirements For ResponseStatus](#requirements-for-responsestatus). -The client MUST check that the nonce received in the `DialAttempt` is the same -as the nonce it sent in the `DialRequest`. If the nonce is different, it MUST +The client MUST check that the nonce received in the `DialBack` is the same as +the nonce it sent in the `DialRequest`. If the nonce is different, it MUST discard this response. The server MUST close the stream after sending the response. The client MUST @@ -105,38 +106,43 @@ close the stream after receiving the response. ### Requirements for DialStatus -On receiving a `DialRequest`, the server first selects an address that it will dial. +On receiving a `DialRequest`, the server first selects an address that it will +dial. -If server chooses to not dial any of the requested addresses, `DialStatus` is -set to `E_DIAL_REFUSED`. In this case no dial attempt was made and -the `addrIdx` sent with the response is meaningless. +If server chooses to not dial any of the requested addresses, `ResponseStatus` +is set to `E_DIAL_REFUSED`. The fields `addrIdx` and `DialStatus` are +meaningless in this case. See [Requirements For +ResponseStatus](#requirements-for-responsestatus). If the server selects an address for dialing, `addrIdx` is set to the -index(zero-based) of the address on the list and the `DialStatus` is -set according to the following consideration: +index(zero-based) of the address on the list and the `DialStatus` is set +according to the following consideration: If the server was unable to connect to the client on the selected address, -`DialStatus` is set to `E_DIAL_ERROR`, indicating the selected address is -not publicly reachable. +`DialStatus` is set to `E_DIAL_ERROR`, indicating the selected address is not +publicly reachable. If the server was able to connect to the client on the selected address, but an -error occured while sending an nonce on the `/libp2p/autonat/2/attempt` stream, -`DialStatus` is set to `E_DIAL_ATTEMPT_ERROR`. This might happen in case of +error occured while sending an nonce on the `/libp2p/autonat/2/dial-back` +stream, `DialStatus` is set to `E_DIAL_BACK_ERROR`. This might happen in case of resource limited situations on client or server, or when either the client or the server is misconfigured. If the server was able to connect to the client and successfully send a nonce on -the `/libp2p/autonat/2/attempt` stream, `DialStatus` is set to `OK`. +the `/libp2p/autonat/2/dial-back` stream, `DialStatus` is set to `OK`. ### Requirements for ResponseStatus The `ResponseStatus` sent by the server in the `DialResponse` message MUST be set according to the following requirements -`E_REQUEST_REJECTED`: the server didn't attempt to serve the request because of -rate limiting, resource limit reached or blacklisting. +`E_REQUEST_REJECTED`: The server didn't serve the request because of rate +limiting, resource limit reached or blacklisting. + +`E_DIAL_REFUSED`: The server didn't dial back any address because it was +incapable of dialing or unwilling to dial any of the requested addresses. -`E_INTERNAL_ERROR`: error not classified within the above error codes occured on +`E_INTERNAL_ERROR`: Error not classified within the above error codes occured on server preventing it from completing the request. `OK`: the server completed the request successfully. A request is considered @@ -194,11 +200,11 @@ if more than 3 servers report a successful dial and to consider an address unreachable if more than 3 servers report unsuccessful dials. Implementations are free to use different heuristics than this one -Servers SHOULD NOT reuse their listening port when making a dial attempt. In -case the client has reused their listen port when dialing out to the server, not +Servers SHOULD NOT reuse their listening port when making a dial back. In case +the client has reused their listen port when dialing out to the server, not reusing the listen port for attempts prevents accidental hole punches. Clients -SHOULD only rely on the nonce and not on the peerID for tallying the dial -attempt as the server is free to use a separate peerID for the dial attempts. +SHOULD only rely on the nonce and not on the peerID for verifying the dial back +as the server is free to use a separate peerID for the dial backs. ## RPC Messages @@ -207,11 +213,13 @@ All RPC messages sent over a stream are prefixed with the message length in bytes, encoded as an unsigned variable length integer as defined by the [multiformats unsigned-varint spec][uvarint-spec]. -All RPC messages on stream `/libp2p/autonat/2/dial` are of type `Message`. A -`DialRequest` message is sent as a `Message` with the `msg` field set to -`DialRequest`. `DialResponse` and `DialDataRequest` are handled similarly. +All RPC messages on stream `/libp2p/autonat/2/dial-request` are of type +`Message`. A `DialRequest` message is sent as a `Message` with the `msg` field +set to `DialRequest`. `DialResponse` and `DialDataRequest` are handled +similarly. -On stream `/libp2p/autonat/2/attempt`, a `DialAttempt` message is sent directly +On stream `/libp2p/autonat/2/dial-back`, a `DialAttempt` message is sent +directly ```proto3 @@ -238,12 +246,10 @@ message DialDataRequest { enum DialStatus { - // Default value to force servers to explicitly set the status - E_INTERNAL_ERROR = 0; - E_DIAL_REFUSED = 100; - E_DIAL_ERROR = 101; - E_ATTEMPT_ERROR = 102; - OK = 200; + UNUSED = 0; + E_DIAL_ERROR = 100; + E_DIAL_BACK_ERROR = 101; + OK = 200; } @@ -251,6 +257,7 @@ message DialResponse { enum ResponseStatus { E_INTERNAL_ERROR = 0; E_REQUEST_REJECTED = 100; + E_DIAL_REFUSED = 101; ResponseStatus_OK = 200; } @@ -265,7 +272,7 @@ message DialDataResponse { } -message DialAttempt { +message DialBack { fixed64 nonce = 1; } ``` From 209b21517fbba4d155d00f9666681dc512e9525f Mon Sep 17 00:00:00 2001 From: sukun Date: Mon, 21 Aug 2023 18:57:09 +0530 Subject: [PATCH 20/25] fix ResponseStatus_OK --- autonat/autonat-v2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 73b6ff4bb..593945984 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -258,7 +258,7 @@ message DialResponse { E_INTERNAL_ERROR = 0; E_REQUEST_REJECTED = 100; E_DIAL_REFUSED = 101; - ResponseStatus_OK = 200; + OK = 200; } ResponseStatus status = 1; From 094089bb2ac9775236fdfa8287f4e5c3015ce7c0 Mon Sep 17 00:00:00 2001 From: sukun Date: Wed, 6 Sep 2023 16:16:47 +0530 Subject: [PATCH 21/25] IPv4 only servers should refuse requests for IPv6 addresses --- autonat/autonat-v2.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 593945984..dc8d14709 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -206,6 +206,9 @@ reusing the listen port for attempts prevents accidental hole punches. Clients SHOULD only rely on the nonce and not on the peerID for verifying the dial back as the server is free to use a separate peerID for the dial backs. +Servers SHOULD determine whether they have IPv6 and IPv4 connectivity. IPv4 only servers SHOULD refuse requests for dialing IPv6 addresses and IPv6 only +servers SHOULD refuse requests for dialing IPv4 addresses. + ## RPC Messages From b4a856bd8c942b419f21b71a21b9bf192a51fd29 Mon Sep 17 00:00:00 2001 From: sukun Date: Mon, 30 Oct 2023 16:55:09 +0530 Subject: [PATCH 22/25] fix dial-request protocol name --- autonat/autonat-v2.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index dc8d14709..3329ac6d0 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -64,7 +64,7 @@ attacks. `autonat v1` disallowed such dials to prevent amplification attacks. A client node wishing to determine reachability of its addresses sends a `DialRequest` message to a server on a stream with protocol ID -`/libp2p/autonat/2/dial`. Each `DialRequest` is sent on a new stream. +`/libp2p/autonat/2/dial-request`. Each `DialRequest` is sent on a new stream. This `DialRequest` message has a list of addresses and a fixed64 `nonce`. The list is ordered in descending order of priority for verification. AutoNAT V2 is @@ -166,7 +166,7 @@ On receiving a `DialRequest`, the server selects the first address it is capable of dialing. If this selected address has a IP different from the client's observed IP, the server sends a `DialDataRequest` message with the selected address's index(zero-based) and `numBytes` set to a sufficiently large value on -the `/libp2p/autonat/2/dial` stream +the `/libp2p/autonat/2/dial-request` stream Upon receiving a `DialDataRequest` message, the client decides whether to accept or reject the cost of dial. If the client rejects the cost, the client resets From 1c766133f6ee43d2545f5785355b47d595624057 Mon Sep 17 00:00:00 2001 From: Sukun Date: Mon, 5 Feb 2024 21:52:09 +0530 Subject: [PATCH 23/25] add a response to the dialback stream --- autonat/autonat-v2.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 3329ac6d0..7959188df 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -84,8 +84,11 @@ skipping Amplification Attack Prevention steps. The server dials the selected address, opens a stream with Protocol ID `/libp2p/autonat/2/dial-back` and sends a `DialBack` message with the nonce -received in the request. The client MUST close this stream after receiving the -`DialBack` message. +received in the request. The client on receiving this message replies with +a `DialBackResponse` message with the status set to `OK`. The client MUST +close this stream after sending the response. The dial back response provides +the server assurance that the message was delivered so that it can close the +connection. Upon completion of the dial back, the server sends a `DialResponse` message to the client node on the `/libp2p/autonat/2/dial-request` stream. The response @@ -278,6 +281,14 @@ message DialDataResponse { message DialBack { fixed64 nonce = 1; } + +message DialBackResponse { + enum DialBackStatus { + OK = 0; + } + + DialBackStatus status = 1; +} ``` [uvarint-spec]: https://github.com/multiformats/unsigned-varint From 03718ef0f2dea4a756a85ba716ee33f97e4a6d6c Mon Sep 17 00:00:00 2001 From: sukun Date: Thu, 20 Jun 2024 17:32:13 +0530 Subject: [PATCH 24/25] allow the client to send slightly more dial data --- autonat/autonat-v2.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 7959188df..576779617 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -179,8 +179,12 @@ client transfers these bytes wrapped in `DialDataResponse` protobufs where the `data` field in each individual protobuf is limited to 4096 bytes in length. This allows implementations to use a small buffer for reading and sending the data. Only the size of the `data` field of `DialDataResponse` protobufs is -counted towards the bytes transferred. Once the server has received numBytes -bytes, it proceeds to dial the selected address. +counted towards the bytes transferred. Once the server has received at least +numBytes bytes, it proceeds to dial the selected address. Servers SHOULD allow +the last `DialDataResponse` message received from the client to be larger than +the minimum required amount. This allows clients to serialize their +`DialDataResponse` message once and reuse it for all Requests. + If an attacker asks a server to dial a victim node, the only benefit the attacker gets is forcing the server and the victim to do a cryptographic From 01952036df0caa02875af35b051667910871f933 Mon Sep 17 00:00:00 2001 From: sukun Date: Thu, 31 Oct 2024 10:01:43 +0530 Subject: [PATCH 25/25] add note that server should not dial any private address --- autonat/autonat-v2.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autonat/autonat-v2.md b/autonat/autonat-v2.md index 576779617..ba1c0918a 100644 --- a/autonat/autonat-v2.md +++ b/autonat/autonat-v2.md @@ -68,9 +68,9 @@ A client node wishing to determine reachability of its addresses sends a This `DialRequest` message has a list of addresses and a fixed64 `nonce`. The list is ordered in descending order of priority for verification. AutoNAT V2 is -only for testing reachability on Public Internet. Client SHOULD NOT send any +primarily for testing reachability on Public Internet. Client SHOULD NOT send any private address as defined in [RFC -1918](https://datatracker.ietf.org/doc/html/rfc1918#section-3) in the list. +1918](https://datatracker.ietf.org/doc/html/rfc1918#section-3) in the list. The Server SHOULD NOT dial any private address. Upon receiving this request, the server selects an address from the list to dial. The server SHOULD use the first address it is willing to dial. The server