Change:
Please see the discussion on for the rationale, but in short, SERVERBUSY is renamed as CONNECTIONREFUSED. A server might refuse a connection for various reasons. SERVER_BUSY is one specific reason. Having an error code for that specific case while not having an error code for generic case is awkward. Hence the rename.
I'm not sure if this was an editorial PR. The previous text talked about a situation where a server was too busy to accept a new connection. The new text generalizes this to a connection refusal for any reason. That's a design change. I also strongly disagree that this issue should There's still on ongoing discussion if an additional error code should be introduced. I can't see how this is not a design issue.
There was previously no requirement to send this error code under any particular circumstances nor any requirement to take action upon receipt of this error code, and therefore those requirements cannot have been changed. This modifies the prose around an error code the server could use to reject a connection attempt to make the recommendation more narrow.
This is spawned from and . The discussion there revealed that SERVER_BUSY has implied semantics that aren't helpful and it would be better to have a more generic message instead. This tracks that. The resolution might be , depending on how we resolve this.
My personal preference is to not merge . A generic error code is more useful. There is a small loss here in that a server has to use a non-intuitive code for the case that it finds it can't continue with a connection due to load, but I am comfortable with that.
was already merged, and there's some question about whether it was actually editorial. This issue tracks the question retroactively. The proposal here is to leave merged / The alternative is to merge , reverting the other PR.
I prefer the generic message, so I would like to see the current state of affairs retained. That is, use CONNECTIONREFUSED going forward (instead of SERVERBUSY), and do not merge . If there is a need for other more specific error codes, we can deal with that separately.
+1 to leaving the current state. CONNECTION_REFUSED is generic enough to be useful in the situation NAME described, and while it's nice to have more specific error codes, I don't think there's an abundant need for a BUSY-like signal, without also getting into the business of prescribing things like time-based retry for QUIC clients.
+1 to retaining the current state. I'm repeating my comment on , but when QUIC is used as the only transport protocol (rather than with happy-eyeballing), it has to be resilient to MITM attacks as TLS over TCP is. TLS over TCP does not have an unauthenticated error code that tells the client to stop trying to connecting to the server. We cannot have such thing in QUIC, because if we do, QUIC becomes more prone to MITM attacker than TLS over TCP is. CONNECTIONREFUSED is fine in this respect, because it does not indicate to the client what the reason was. But error codes like SERVERBUSY or TRY_LATER would have the implications that we cannot have.
Closing as proposed.
Assume that a server has a blacklist of IP addresses, that it doesn't accept connections from. One way of implementing this would be to drop all packets from those IP addresses. But this would lead to clients time out, which is sad. Let's say the server wants to be more helpful and allow the client to quickly recover from this situation. Sending a CONNECTIONCLOSE would seem like the right thing to do in that case. However, we don't have an error code that fits here. SERVERBUSY is used to reject connection attempts, but it's a bad fit here. Retrying later is not likely to succeed in this case. APPLICATIONERROR also seems too broad, as it's used for any application-level error (in Initial packets). Do we need a new error code to cover this use case? Something like CONNECTIONREFUSED?
If my reading of our code is correct, we're using PROTOCOLVIOLATION for that case, but that's not a very informative choice. In gQUIC, we used public reset for this(as you probably remember), but stateless reset is post-handshake only. CONNECTIONREFUSED sounds better, but I think an important question is wether we expect clients to treat this error code differently than other error codes which occur during the handshake. If we're not going to treat it differently, then it matters less. That being said, I'm a general fan of more granular error codes for debugging.
I guess a reasonable policy regarding errors received during the handshake would be: INVALIDTOKEN: try again immediately SERVERBUSY: try again after a few minutes CONNECTIONREFUSED: don't try again for a day / week or so other errors: I can see TRANSPORTPARAMETERERROR and FRAMEENCODINGERROR (and their generic subsitute, PROTOCOLVIOLATION) being reasonably sent in response to a ClientHello. So that means that either your or your peer's implementation is broken, so maybe you shouldn't speak QUIC at all?
I tend to think that adding an error code that affects client behavior is not a good idea, when that error code is intended to indicate a handshake failure. The signal carried by an Initial packet or a Handshake packet is not authenticated, and we should be wary of using that to convey something that affects more than just that connection. In case of this example, CONNECTIONREFUSED would become an attractive tool for MITM attackers, because it can be used for preventing the client for reconnecting for as long as a day or a week. If we need these signal, I think that they have to be carried at the application-level after the QUIC handshake concludes. In case of HTTP, we already have 503 + Retry-After header field. To summarize, I think my position is: We should not add error codes that affect the client behavior after that connection gets closed. We might want to get rid of SERVERBUSY, or at least state that a client cannot really tell if the server was really busy, as the signal is not authenticated.
We accept this risk in QUIC v1. Removing error codes to mitigate the MITM vulnerability seems like a piecemeal "solution" to a problem that can only be solved by decent cryptography. For the MITM the easiest way is probably injection a Version Negotiation packet that indicates that there are no compatible versions between the client and the server. I'd expect most client implementations to give up a dial attempt in this case, which in effect would be very similar to the actions taken in response to a CONNECTION_REFUSED.
I disagree with that view. Handshake is never going to be disruption resistant, unless there is a shared secret. There are ways of raising the probability of endpoints having shared secret (that we discussed and postponed to v2), but there would always be a case where there is no shared secret. We always need to limit the impact of attacker injecting signals to an unauthenticated channel. I'd also reiterate that in HTTP we already have a way of asking clients to revisit the server after certain amount of time. TLS does not have a way of signaling that. Assuming that HTTP developers are fine with what we have in HTTP + TLS, I do not think there is good justification to add a new feature that has negative impact on security. Giving up one attempt is fine. Having a signal that asks to give up future connection attempts is the problem.
HTTP has 503 to reject request when a server is busy, and still we have SERVER_BUSY at the QUIC layer. There's a value in rejecting connection attempts before spending resources (and round trips) on a handshake. Furthermore, there are other application protocols than HTTP. In my use case, nodes in a p2p network want to enforce a local blacklist of IP addresses. In TCP, they can easily do that by resetting the connection, and before spending any resources on the TLS handshake. Having to go through the whole QUIC handshake with a blacklisted peer before being able to reject them would be a serious regression compared to TCP. I'm not sure how browsers handle a Version Negotiation packet that indicates that there are no matching versions. I doubt that they'd try QUIC again for the next request just a few seconds later. Intuitively, I'd say that a longer period combined with an exponential backoff would be a reasonable thing to implement. Maybe NAME and NAME can shed some light on this?
Yes, TCP or TLS can reject one attempt. QUIC is currently aligned to that, and that's fine. I'd point out that how aggressively browsers try connecting in QUIC depends on if they do happy-eyeballing. When browsers do happy-eyeballing, no matter how less you do in QUIC, the probability of establishing a connection always stays higher (or as high as) just doing TLS. The question is when browsers start doing just QUIC. Would they prefer to cache previous connection failures, as a way to not even try to reconnect when a user requests doing so?
As a way of moving forward, I'd propose renaming SERVERBUSY to CONNECTIONREFUSED, with no recommendation on how the client should react to that. The rationale behind providing no recommendation is as stated above. QUIC needs to be strong against MITM attacks, at least as TLS over TCP is. TLS over TCP does not have an error code that tells the client to refrain from retrying for some amount of time. To achieve parity, we cannot have that. AFAIK, our experience with HTTPS tells us that there is no need for such an error code. The rationale behind the renaming is that at the moment we do not have a generic error code for refusing connections, even though we have SERVERBUSY, which indicates a specific reason of refusing a connection. Therefore, we need CONNECTIONREFUSED. We can rename SERVERBUSY to CONNECTIONREFUSED. SERVERBUSY does not need to be preserved, since, as stated, the client behavior cannot vary depending on an unauthenticated error code. For debugging purposes, endpoints are free to use the reasonphrase field. WDYT?
I don't think that renaming an existing error will solve my problem. Otherwise, I would've just used that error... The point I was trying to make here is that clients will already make decisions about future connection attempts based on unauthenticated information, both sent in CONNECTION_CLOSE error codes as well as in Version Negotiation packets. Adding one more error doesn't seem to exacerbate that situation, while at the same time enabling a way of blacklisting IP addresses that was possible with TCP and is currently not possible using QUIC.
As stated, my view is that your problem (of sending a signal that affects future connections) should only be solved by sending something after the handshake succeeds. And for HTTP, we already have that signal (i.e. 503 + Retry-After). Therefore, defining such signal does not have to be done in QUICv1. I disagree with that view. If the specification can be read as such, then I think we need to make changes. A client should refrain from using an unauthenticated signal for determining what to do with future connection attempts. It does not matter what that unauthenticated signal is (e.g., an error code in Initial packet, failed version negotiation). Any unsuccessful handshake attempt should be considered as an error of that particular attempt. Otherwise, QUIC handshake becomes prone to MITM attacks than TLS over TCP. If we are to agree that clients should not use an unauthenticated error code in determining what to do in the future, there is no point in having multiple error codes indicating that the server has deliberately refused a handshake. It is sufficient to have just one error code that let's the server say, hey, nothing is wrong with us, but I refuse to accept a connection.
NAME I believe it's up to the client as to how unauthenticated signals are used. There are a lot of ways to interrupt a QUIC handshake early on, and this is one of many. Chrome does exponential backoff with a 5 minute time period anytime the handshake fails, whether it's due to VN, connection close, handshake timeout, etc. In practice, VN and Alt-Svc typically agree, so VN is very rare and connection closes are more common. I can't argue Chrome's algorithm is optimal, but if handshakes failed in the recent past, it seems sub-optimal to try QUIC again immediately.
NAME I think that is a fine thing to do as long as Chrome is happy-eyeballing with TLS-over-TCP. But as a specification, it is my understanding that happy-eyeballing is not a prerequisite. I'd assume that we'd want applications that use QUIC only to be as ignorant against unauthenticated signals as those using TLS-over-TCP is.
If only QUIC is used, then I agree the behavior should match that of TLS over TCP.
I like , but it is a semantic change. It makes the existing code more general, which is appropriate. We would need to make this issue a design issue to do that though.
This is practically impossible. To function reliably on the internet a client has to use heuristics and unauthenticated information in order to provide a good user experience and not overwhelm server resources. As a trivial example, if I wrote a client which completely ignores things like VN and immediately retries a QUIC connection, I am locked into supporting that version on my server indefinitely, since my client will now effectively DOS me if I remove support for this version from the server. This is unacceptable for internet scale. Additionally, rejecting a connection post-handshake makes fallback to TCP more complicated -- it is much simpler to use a happy eyeballs mechanism and "race" TCP and QUIC, stopping the TCP connection once the handshake is successful. I am highly sympathetic to NAME point here. As has been said already, we already have 100% reliable and trivially implementable way to interrupt the handshake in v1 with version negotiation. Having an additional signal which is more explicit can only help clients making new connection decisions. We don't necessarily have to prescribe what the client does, but we can say why a server may issue certain errors during the handshake.
NAME I'd emphasize that the specification does not talk about happy eyeballing. We should concentrate on discussing what to do when only QUIC is being used. Having that said, I'd hope that we can agree on one principle: QUIC cannot respect unauthenticated information any more than TLS over TCP does. Otherwise, QUIC becomes more prone to MITM attacks that TLS over TCP is - a disobedience to our charter. That might be true, but our experience with TLS over TCP is that we do not need an error code for the purpose. FWIW, you do not need a version negotiation packet. Per my understanding, any server can send a private error code. Or a reason phrase explaining things. Am I correct to understand that the reason you are advocating for having more error codes is to allow a server to control the behavior of client for future connection attempts? Assuming that is the case, I'd oppose to having such error codes regardless of how we would describe them. Because IMO that is effectively about making QUIC more prone to attacks than TLS over TCP is.
I think that NAME is entirely right here. The renaming of the error code is useful as it removes the implication of a semantic that would be destructive. That implication is that the code is like a 503, which comes with the further implication that holding back for some time is desirable (due to the common use of Retry-After with 503). QUIC has no Retry-After, so any implied time delay is bogus. As mentioned, that browsers might sit and wait is a policy decision that makes sense in the current state of the world. I'm going to merge the PR. But I get the sense that this is not directly addressing the issue. For the issue itself, I think that the only real resolution is: that's tough, but too bad. As numerous people have said, this phase of connection establishment is ripe for DoS and we have chosen to accept that risk in QUIC version 1.
Based on a request on . I have also opened .
I can see why this issue being classified as a design change, though at the same time, I think that was a good editorial change. made things betterWe now have an error code for server indicating that it rejected the connection even though nothing seemed to be wrong with the handshake. Without having CONNECTIONREFUSEDERROR, a client had to guess if PROTOCOLVIOLATION meant a problem in the handshake, or if it was simply a refusal. From now on, on this issue, I hope that we can concentrate on discussing if we want to have more than one error code indicating the specifics of connection refusals (e.g., SERVERBUSY). Unsurprisingly, my position is that we cannot have specific codes due to the reasons laid out above.
I agree with NAME in that was good (and I don't want to revert it). I also don't think we need anything more than an appropriately generic error code for the stated problem, so I believe that that PR was adequate.
I agree with NAME I think we should and I would argue that we should do a new issue if anyone wants more specific error codes.
I love more specific error codes, but many in the WG do not, so I still like given the constraints we're working with.
NAME No introduction of error codes can possibly make QUIC more prone to this than TCP + TLS. A TCP connection can be completely interrupted at any time with an unauthenticated RST. In that sense I don't think it's possible to make QUIC more prone to MITM attacks than TCP + TLS, since at the very least we only have a small window for the similar MITM interruption. It's not a fair comparison, as TCP does not have an opportunity to fallback to anything. You posit that we have to view the QUIC specification in isolation from the existence TCP or any "happy eyeballs" mechanism, and I don't totally disagree but I don't think it follows we should make decision ignoring the realities of deploying QUIC on the Internet. That reality is, today, that QUIC will not work 100%, whereas we expect TCP to function. If we can add things to the specification which aid in a seamless coexistence of QUIC and alternatives, why would we not? I don't think I fully understand your point here. You asserted that the client "should refrain from using an unauthenticated signal for determining what to do with future connection attempts". I maintain that this is fantastical. The client has to do something with future connection attempts whether we prescribe a behavior or not, and as such it is abundantly useful to be able to influence, if not dictate, the behavior of clients with more explicit signals from the server. As it is I am fine with leaving this discussion with the generic CONNECTION_REFUSED and calling it a day, but I think there's value in discussing the real-world considerations here, which was NAME original motivation.
Is anyone arguing for leaving this issue open? If folks still want to continue discussing, I propose that we open a new issue to do it.
No response to NAME question in ten days; it looks like this issue is closed.
Closing then.
Spawned from . NAME typed: "Probably not for this change, but do we really want to encourage people to be estimating delays in the kernel? Because it seems like the point of some of the other changes is to include that sort of delay in the RTT estimate. What matters here is end-to-end delays - the time between when the packet was consumed and when the packet containing the ACK was sent out. Yes, it's true that you get a bigger RTT estimate if you don't correct for delays in the kernel or NIC, but those are just another piece of the plumbing now. If we fail to cover things like scheduling delays in the RTT estimate, we're just deluding ourselves about where the delays are." NAME typed: "Yeah, I was thinking about this too, but it's text that we already had. FWIW, I think it's useful to separate out these delays, not because they shouldn't get used for PTO estimates, but because knowing component delays might be helpful. That said, I don't have a good use case for these component delays yet. OTOH, I've started that separation between stack-controlled delays and other delays in the text, it might be worth using that distinction here, and arguing that these delays ought to be part of the path RTT since they're not controlled delays."
:+1: I share the view with NAME and NAME that ack-delay should represent only the delay introduced intentionally by the QUIC stack. "stack-controlled delay" sounds like a good way of explaining them.
+1 to the idea that ack-delay should be intentionally introduced delay, since almost anything on path(including the local host NIC/OS/Firmware) can delay things without the receiver's control.
From London: Platform delays SHOULD be included in RTT, but ack_delay should only include intentionally introduced delays, not platform delays.

Discussion:
specification, the server proceeds with the handshake (handshake). This commits the server to the version that the client selected. <del> If a server isn't currently accepting any new connections, it SHOULD send an Initial packet containing a CONNECTION_CLOSE frame with error code SERVER_BUSY. </del> <ins> If a server refuses to accept a new connection, it SHOULD send an Initial packet containing a CONNECTION_CLOSE frame with error code CONNECTION_REFUSED. </ins> If the packet is a 0-RTT packet, the server MAY buffer a limited number of these packets in anticipation of a late-arriving Initial

The endpoint encountered an internal error and cannot continue with the connection. <del> The server is currently busy and does not accept any new connections. </del> <ins> The server refused to accept a new connection. </ins> An endpoint received more data than it permitted in its advertised data limits; see flow-control.