Subject: [quicwg/base-drafts] Simplify the client's PTO code by allowing the server to send a PING (#3161)

From: ianswett <[email-masked]>
Date: Mon, 28 Oct 2019 10:30:25 -0700

At the New York QUIC interim, we dealt with the problem of the server being blocked by the amplification factor by making it arm the PTO until it knew the server had validated the path, even if it had nothing to send.

>From the recovery [pseudocde](https://github.com/quicwg/base-drafts/blob/master/draft-ietf-quic-recovery.md#on-timeout):
`  if (endpoint is client without 1-RTT keys):
    // Client sends an anti-deadlock packet: Initial is padded
    // to earn more anti-amplification credit,
    // a Handshake packet proves address ownership.
    if (has Handshake keys):
      SendOneAckElicitingHandshakePacket()
    else:
      SendOneAckElicitingPaddedInitialPacket()`

When reviewing https://github.com/quicwg/base-drafts/pull/3057, I realized that if we allowed the server to send a PING-only packet when the PTO fired and the amplification factor had been reached, then that would elicit a padded Initial ACK frame in response, which also fixes the issue.

@martinduke though that was a simpler solution than the current approach and this is a bit of an edge case, so simple is probably 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/issues/3161

---

From: martinduke <[email-masked]>
Date: Mon, 28 Oct 2019 10:33:58 -0700

In a strict sense, this means that a client can use N bytes to trigger the server to send 3N + epsilon bytes, but this fits in nicely with Probes generally not being subject to sending limits. It is somewhat gross but more elegant that a client timer with nothing in flight.

-- 
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/issues/3161#issuecomment-547061014

---

From: Kazuho Oku <[email-masked]>
Date: Mon, 28 Oct 2019 22:21:45 -0700

> In a strict sense, this means that a client can use N bytes to trigger the server to send 3N + epsilon bytes

I think this might be concerning, though I could well be wrong.

Consider the case of an attacker acting as a client, using the IP address of a victim as the source address. The attacker can spoof an Initial packet that carries a Client Hello and another Initial packet that carries an ACK for the server's Initial packet that carried the Server Hello. An attacker can build the correct second packet assuming that the server uses the DCID of the client's Initial packet as it's CID (or the CID is zero length), and that the server's first packet number can be guessed.

If that ACK contains a delay that indicate to the server that the RTT is very small (e.g., 1ms), then the PTO could be as small as that value. Then, if the server's connection timeout is say 1000ms, the server might send 1000 packets to the victim before dropping the connection.

Am I missing something?

-- 
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/issues/3161#issuecomment-547261010

---

From: ianswett <[email-masked]>
Date: Tue, 29 Oct 2019 09:42:39 -0700

PTO always uses exponential backoff, so if the first PTO was 1ms, that'd be ~10 PTOs, not 1000.

In terms of total bytes sent, 10 packets with a 1 byte payload are still smaller than a single full-sized packet.

-- 
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/issues/3161#issuecomment-547517723

---

From: Christian Huitema <[email-masked]>
Date: Tue, 29 Oct 2019 12:25:39 -0700

Isn't this concern why we designed the Retry mechanism in the first place? If we are concerned about address spoofing attacks, the simple solution might be to generalize retry. As in, "if the server can predict that its first flight will contain more than 3 times the amount of data received with the client's Initial packets, then it SHOULD verify the source address using Retry."

-- 
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/issues/3161#issuecomment-547589874

---

From: ianswett <[email-masked]>
Date: Tue, 29 Oct 2019 20:22:57 +0000 (UTC)

My concern is that the current anti-deadlock mechanism is fairly awkward and rarely needed, which makes me concerned it will be incorrectly implemented.  By sending a PING only packet, the server is essentially informing the client it has something to send, but can't send it due to the amplification limit.

The deadlock happens when the client's Initial is acknowledged, but the client doesn't receive the server's Initial, so it can't decrypt any handshake packets.  In this state, the client has nothing to send.  It can happen even if the server's first flight fits into 3 packets.  But it's expected that typically servers will bundle their ACK of the client's Initial with the server Initial, and this will be rare in practice.

-- 
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/issues/3161#issuecomment-547612280

---

From: Christian Huitema <[email-masked]>
Date: Tue, 29 Oct 2019 14:12:00 -0700

@ianswett: Fair. Due to the risk of amplification attacks, the server is probably not willing to retransmit Initial packets on timer. In that case, the current spec is a bit too lose. It should say that "If the sender is not willing to retransmit Initial packets due to protection against amplification attacks, it MUST NOT acknowledge the client's initial packet." So basically, either the server verifies the client's address with the Retry/Token mechanism, or it MUST NOT acknowledge client initial packets before continuity can be verified by obtaining an ACK from the client.

-- 
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/issues/3161#issuecomment-547631456

---

From: ianswett <[email-masked]>
Date: Tue, 29 Oct 2019 14:16:52 -0700

That's an interesting suggestion, but I think it require some non-trivial changes to the server's logic in addition to not sending ACKs?  Something similar to what's described in #3080.

It may be worth opening up an issue for allowing the server to not arm a timer until the path has been validated, because I think that's a wider issue than just this PR?

-- 
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/issues/3161#issuecomment-547633195

---

From: Christian Huitema <[email-masked]>
Date: Tue, 29 Oct 2019 14:28:27 -0700

I agree that having the server repeat a PING on timer may also work, and that the ACK of the randomly chosen Server's CID would make a reasonable address ownership test. ACK of the PING would naturally unlock any transmission that is pending address ownership.

Kazuho objects that this his hackable if the chosen Server's CID is guessable. We don't have a "non-guessable" requirement in the spec. In that case, the natural fix is the challenge/response mechanism.

-- 
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/issues/3161#issuecomment-547637350

---

From: Kazuho Oku <[email-masked]>
Date: Tue, 29 Oct 2019 14:33:10 -0700

@ianswett 
> PTO always uses exponential backoff, so if the first PTO was 1ms, that'd be ~10 PTOs, not 1000.

Ah. Thank you for pointing that out. Though it seems that the amount of the data that the server can send could go like 4x the amount of data that the client has sent rather than the current 3x.

To paraphrase, I think what we might be trading here is the initial amount of data the server can send vs. the capability of sending PINGs. My view is that the former is a scarce resource, and that I'd prefer having the more space in the former even if requires us to have some complexity.

-- 
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/issues/3161#issuecomment-547639187

---

From: martinduke <[email-masked]>
Date: Tue, 29 Oct 2019 15:10:31 -0700

@kazuho  An Initial ping is maybe 45 bytes. So 10 pings makes the amplification ~3.4.

I was under the impression that the 3 multiplier was semi-arbitrary, so I'm unconcerned about a fractional increase.

-- 
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/issues/3161#issuecomment-547651924

---

From: Jana Iyengar <[email-masked]>
Date: Tue, 29 Oct 2019 18:31:39 -0700

I have a couple of thoughts. First, this change is not necessarily an overall simplification. It makes things slightly simpler at the client, but it adds about as much code to the server.  Additional code has to be added at the server to the PTO response to only send a PING and not a retransmission when the path is not yet validated.

Second, I think it does open up an interesting attack vector that is currently absent. @kazuho notes
that there is amplification, but I don't think byte amplification is the interesting one here, since the total load is going to be < 1 MTU. The interesting amplification here is packet amplification, which is a useful attack since UDP and packet processing costs at endpoints / clients is a relatively high cost. And you can get a fair number of packets back from the server for spoofing a couple of packets, in spite of the exponential backoff.

I'll grant that the attack is a bit of a stretch, and does require that the attacker guess the server's CID and packet number. Though, as @kazuho pointed out to me offline, we allow the server to use a zero-length CID. 

I don't think this change is necessary. And on balance, I don't think this change is a good one.

-- 
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/issues/3161#issuecomment-547700025

---

From: Jana Iyengar <[email-masked]>
Date: Tue, 29 Oct 2019 18:56:17 -0700

I do like the idea of sending a PING frame from the server as a signal to the client that it is blocked. I think we can suggest that as an optimization that a clever server might implement _under_ the 3x limit.

-- 
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/issues/3161#issuecomment-547704961

---

From: Kazuho Oku <[email-masked]>
Date: Tue, 29 Oct 2019 19:12:59 -0700

@janaiyengar 
> I do like the idea of sending a PING frame from the server as a signal to the client that it is blocked. I think we can suggest that as an optimization that a clever server might implement _under_ the 3x limit, or when it reaches the 3x limit.

While endpoints can do that, I am not sure if it is a good idea to endorse such a behavior in the specification.

The reason we have so far been fine with having the 3x limit being applied until the Handshake packets are exchanged is because we assume that the size of ServerHello would not be that large compared to the size of the ClientHello, and therefore that the chance of hitting 3x limit before path validation is minimal. It's a hack.

If we are to address the corner cases that the server hits after sending 3x amount of data, then I think we should fix the hack than putting another hack (i.e. the PING that signals 3x limit) above the existing hack.

I mean, if we think that defining a way that allows the server to unblock the 3x limit is important, we should be considering allowing the use of PATH_CHALLENGE / PATH_RESPONSE frames in Initial packets.

-- 
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/issues/3161#issuecomment-547708352