Subject: [quicwg/base-drafts] Address validation for connection migration (#732)

From: Martin Thomson <[email-masked]>
Date: Tue, 15 Aug 2017 02:45:07 +0000 (UTC)

This has been much-discussed, and it's a relatively isolated change, so I did it.

This modifies PING to have an optional payload and adds a PONG frame to echo
the PING.  An empty PING generates an ACK; a PING with a payload demands a
PONG.

Generating an unguessable PING is the basis of mid-connection address
validation.  If the PING is sent on the new path, and the PONG comes back, then
the remote address is probably OK to use.

I've taken the discussion in the issue into consideration here.  There's a lot
of potential nuance to capture in terms of how an endpoint might reduce and
restore send rates, but I've done what I can to thread the gap between allowing
unbounded sending along new and untested paths and allowing connections to get
back to doing business.

It's annoying that this makes PING and PONG so disparate.  I think that we have
a re-ordering of frames in our near future to correct minor infidelities like
this.  I didn't want to do that here and pollute this PR though.

Closes #161.
You can view, comment on, or merge this pull request online at:

  https://github.com/quicwg/base-drafts/pull/732

-- Commit Summary --

  * Address validation for connection migration

-- File Changes --

    M draft-ietf-quic-transport.md (122)

-- Patch Links --

https://github.com/quicwg/base-drafts/pull/732.patch
https://github.com/quicwg/base-drafts/pull/732.diff

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732

---

From: Marten Seemann <[email-masked]>
Date: Tue, 15 Aug 2017 02:48:56 +0000 (UTC)

marten-seemann commented on this pull request.

> @@ -846,6 +846,7 @@ explained in more detail as they are referenced later in the document.
 | 0x0a        | STREAM_ID_NEEDED  | {{frame-stream-id-needed}}  |
 | 0x0b        | NEW_CONNECTION_ID | {{frame-new-connection-id}} |
 | 0x0c        | STOP_SENDING      | {{frame-stop-sending}}      |
+| 0x07        | PONG              | {{frame-pong}}              |

This should be `0x0d`.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#pullrequestreview-56252662

---

From: Martin Thomson <[email-masked]>
Date: Mon, 14 Aug 2017 19:50:17 -0700

@martinthomson pushed 1 commit.

26ca55b  0x0d

-- 
You are receiving this because you are subscribed to this thread.
View it on GitHub:
https://github.com/quicwg/base-drafts/pull/732/files/ad334a4193453ef6fe0488852514dcd1cbf7c28d..26ca55b07667ee38c06c0297cb1c44cdc8063654

---

From: ianswett <[email-masked]>
Date: Tue, 15 Aug 2017 17:28:18 +0000 (UTC)

ianswett commented on this pull request.

Thanks for trying to resolve this.  Some comments about immediately moving to the new path.

> @@ -1351,6 +1355,14 @@ connection when they move networks.  This includes state that can be hard to
 recover such as outstanding requests, which might otherwise be lost with no easy
 way to retry them.
 
+An endpoint that receives packets that contain a source IP address and port that
+has not yet been used MUST start sending new packets with those values as a
+destination IP address and port.  Packets exchanged between endpoints now
+follows a new path.  An endpoint MUST validate that its peer can receive packets

nit: follows -> follow

> @@ -1351,6 +1355,14 @@ connection when they move networks.  This includes state that can be hard to
 recover such as outstanding requests, which might otherwise be lost with no easy
 way to retry them.
 
+An endpoint that receives packets that contain a source IP address and port that

This is reasonable, but I'm not sure it's what we always want.  I think we should have a special case for PING/PONG to allow proactive path validation.  If you receive only a PING, only send the PONG down that path.  If you receive other packets, then send all traffic down the new path?

> +An endpoint that see a new source IP address and port (or just a new source
+port) on packets from its peer is likely seeing a connection migration at the
+peer.
+
+However, it is also possible that the peer is spoofing its source address in
+order to cause the endpoint to send excessive amounts of data to an unwilling
+host.  If the endpoint sends significantly more data than the peer, connection
+migration might be used to amplify the volume of data that an attacker can
+generate toward a victim.
+
+Thus, when seeing a new remote transport address, an endpoint MUST verify that
+its peer can receive and respond to packets at that new address.  By providing
+copies of the frames that it receives, the peer proves that it is receiving
+packets at the new address and consents to receive data.
+
+Prior to validating the new remote address, and endpoint MUST limit the rate of

Nit: I think amount matters more than rate for amplification attacks.

> @@ -1351,6 +1355,14 @@ connection when they move networks.  This includes state that can be hard to
 recover such as outstanding requests, which might otherwise be lost with no easy
 way to retry them.
 
+An endpoint that receives packets that contain a source IP address and port that

Also, reordering can easily cause one to receive a packet from the new path, then then old path, then the new path.  I think it'd be helpful to provide some advice about sending in response to the packet with the largest packet number?

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#pullrequestreview-56406933

---

From: Mike Bishop <[email-masked]>
Date: Tue, 15 Aug 2017 18:02:12 +0000 (UTC)

MikeBishop commented on this pull request.

> +more packets.
+
+If the remote address is validated successfully, the endpoint MAY increase the
+rate that it sends on the new path using the state from the previous path.  The
+capacity available on the new path might not be the same as the old path.  An
+endpoint MUST NOT restore its send rate unless it is reasonably sure that the
+path is the same as the previous path.  For instance, a change in only port
+number is likely indicative of a rebinding in a middlebox and not a complete
+change in path.  This determination likely depends on heuristics, which could be
+imperfect; if the new path capacity is significantly reduced, ultimately this
+relies on the congestion controller responding to congestion signals and reduce
+send rates appropriately.
+
+After verifying an address, the endpoint might wish to update address validation
+tokens ({{address-validation}}) that it has issued to its peer.  Previous tokens
+might have been invalidated by the migration.

Is this an RFC6919 MAY WISH TO?  Why not SHOULD update ... if the tokens were invalidated?

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#pullrequestreview-56419121

---

From: Martin Thomson <[email-masked]>
Date: Thu, 17 Aug 2017 05:04:22 +0000 (UTC)

martinthomson commented on this pull request.

I didn't want to accidentally add multipath here.  The preemptive thing is a fine idea, which I encourage you to put in an issue, though I suspect that we'll want to park it.

BTW, I do want to add multipath.  I'm itching to do multipath, but I hear that there are dragons and we're not done here yet.

The point about reordering is certainly valid.  The new path could be shorter, for instance.  Changes inbound.

I think that we can limit both here.

+

Well, not all tokens are invalidated.  I'll reword to be more in the form of "do X if Y".

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#discussion_r133623312

---

From: Martin Thomson <[email-masked]>
Date: Thu, 17 Aug 2017 05:04:46 +0000 (UTC)

9bfc5d6  Review comments

-- 
You are receiving this because you are subscribed to this thread.
View it on GitHub:
https://github.com/quicwg/base-drafts/pull/732/files/26ca55b07667ee38c06c0297cb1c44cdc8063654..9bfc5d673d99131cc5f709562293a959644de923

---

From: ianswett <[email-masked]>
Date: Thu, 17 Aug 2017 14:55:07 +0000 (UTC)

Ok, so this is explicitly to handle unintentional port/IP migration and intentional port/IP migration without changing paths(ie: privacy preservation)?  I wouldn't call that connection migration, which I think of as explicitly moving from one path to the other, but not using both paths at once.  I'll re-read this with that in mind.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#discussion_r133729566

---

From: Ryan Hamilton <[email-masked]>
Date: Thu, 17 Aug 2017 19:28:26 +0000 (UTC)

It seems like sending a PING and waiting for an ACK of that packet would be roughly equivalent to sending a PING with a token and waiting to receive a PONG with that token. Would PING + ACK be sufficient? (I'm probably not thinking it through clearly) If not, can you elaborate what PING + PONG mitigates that PING + ACK does not? 

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#issuecomment-323170821

---

From: Mike Bishop <[email-masked]>
Date: Thu, 17 Aug 2017 19:32:22 +0000 (UTC)

Yes, since the response is allowed to be from the old IP, this is where a false ACK would cause pain.  If you were to do a packet number jump so the token were the new packet number, maybe -- but then you get into weird states where you don't want to disclose the higher packet number to the old IP, so you're maintaining parallel last-send packet numbers, and....  Ugh.

But I agree, PING with payload feels a bit weird.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#issuecomment-323171725

---

From: Martin Thomson <[email-masked]>
Date: Thu, 17 Aug 2017 18:40:44 -0700

It's intended to be applicable to both.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#discussion_r133865461

---

From: Martin Thomson <[email-masked]>
Date: Fri, 18 Aug 2017 01:46:01 +0000 (UTC)

As Mike says, PING + ACK isn't sufficient.  The attack this defends against is where the attacker uses their real address to make a connection and open the congestion window, then they spoof a "migration" and cause the data that the other side is sending to be sent to a victim.  In that case, the attacker can usually guess what packet to ACK and just pretend to receive the PING.

I don't know how PING with payload is weird.  [h2 does it](http://httpwg.org/specs/rfc7540.html#PING).  And [so does ICMP](https://tools.ietf.org/html/rfc792#page-14) - though it's called "Echo" and "Echo Reply" there (which is so easy to forget).

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#issuecomment-323237961

---

From: MikkelFJ <[email-masked]>
Date: Fri, 18 Aug 2017 08:35:09 +0000 (UTC)

How is it possible to spoof anything once 1RTT encryption is in place? If it is not in place, why would you want to allow connection migration when you can use stateless retry? It seems to be two separate problems.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#issuecomment-323294812

---

From: Martin Thomson <[email-masked]>
Date: Mon, 21 Aug 2017 06:33:34 +0000 (UTC)

Yes, in this attack, the attacker has the keys, but not the address.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#issuecomment-323657951

---

From: Christian Huitema <[email-masked]>
Date: Wed, 23 Aug 2017 00:36:18 +0000 (UTC)

huitema commented on this pull request.

I like the idea, but we need to take care of the potential for DOS.

> +Prior to validating the new remote address, and endpoint MUST limit the amount
+of data and packets that it sends to its peer.  At a minimum, this needs to
+consider the possibility that packets are sent without congestion feedback.
+
+Once a connection is established, address validation is relatively simple (see
+{{address-validation}} for the process that is used during the handshake).  An
+endpoint validates a remote address by sending a PING frame containing a payload
+that is hard to guess.  This frame MUST be sent in a packet that is sent to the
+new address.  Once a PONG frame containing the same payload is received, the
+address is considered to be valid.  The PONG frame can use any path on its
+return.  A PING frame containing 12 randomly generated {{?RFC4086}} octets is
+sufficient to ensure that it is easier to receive the packet than it is to guess
+the value correctly.
+
+If validation of the new remote address fails, after allowing enough time for
+possible loss and recovery of packets carrying PING and PONG frames, the

This opens a man-on-the-side denial of service attack on established connections. Obtain a valid packet from peer A, and race a copy to peer B from a different IP address. B receives the packet, the decryption works, B thus sends a PING to the new address, the PONG of course never comes back, and B drop the connection. Not good.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#pullrequestreview-57947672

---

From: Martin Thomson <[email-masked]>
Date: Tue, 22 Aug 2017 17:51:06 -0700

Firstly, this isn't new, but it suggests some more work (more on this below).

I considered this, but didn't give it enough thought.  The reason I dismissed this was that the very next packet from the victim will come from a genuine address and it will appear to be yet another migration, correcting the problem.  As long as that happens, the attacker only causes a few packets to be sent to it.  But then the next packet might take too long to arrive to be relevant.

That suggests some changes:

1. Abandon any tests on a previous address if a packet from another new address is seen.  (Use packet numbers to ensure that you can detect the most recent packet.)
2. Require that the endpoint that observes the migration ping the old address as well.

The first ensures that that the raced packet can't cause a flip to stick.  Unless the attacker can reliably capture and win a race on every packet, thereby denying service.

The second ensures that if this attack happens and the old address remains valid, a packet will be seen on the old address, causing the migration to switch back.

Does that work?

Or, we could design multipath...

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#discussion_r134635373

---

From: Christian Huitema <[email-masked]>
Date: Wed, 23 Aug 2017 04:04:37 +0000 (UTC)

I replied to your PR, Martin. But I think the key is to break the possibility for third parties to forward PING and PONG. I don't think we can use crypto to verify the source address, but we might request that packets knowingly sent from a new IP be sent with a new connection ID. Of course, there are all kinds of dragons hiding behind the word knowingly.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/732#discussion_r134652939