// Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build go1.21 package quic import "time" type pathState struct { // Response to a peer's PATH_CHALLENGE. // This is not a sentVal, because we don't resend lost PATH_RESPONSE frames. // We only track the most recent PATH_CHALLENGE. // If the peer sends a second PATH_CHALLENGE before we respond to the first, // we'll drop the first response. sendPathResponse pathResponseType data pathChallengeData } // pathChallengeData is data carried in a PATH_CHALLENGE or PATH_RESPONSE frame. type pathChallengeData [64 / 8]byte type pathResponseType uint8 const ( pathResponseNotNeeded = pathResponseType(iota) pathResponseSmall // send PATH_RESPONSE, do not expand datagram pathResponseExpanded // send PATH_RESPONSE, expand datagram to 1200 bytes ) func (c *Conn) handlePathChallenge(_ time.Time, dgram *datagram, data pathChallengeData) { // A PATH_RESPONSE is sent in a datagram expanded to 1200 bytes, // except when this would exceed the anti-amplification limit. // // Rather than maintaining anti-amplification state for each path // we may be sending a PATH_RESPONSE on, follow the following heuristic: // // If we receive a PATH_CHALLENGE in an expanded datagram, // respond with an expanded datagram. // // If we receive a PATH_CHALLENGE in a non-expanded datagram, // then the peer is presumably blocked by its own anti-amplification limit. // Respond with a non-expanded datagram. Receiving this PATH_RESPONSE // will validate the path to the peer, remove its anti-amplification limit, // and permit it to send a followup PATH_CHALLENGE in an expanded datagram. // https://www.rfc-editor.org/rfc/rfc9000.html#section-8.2.1 if len(dgram.b) >= smallestMaxDatagramSize { c.path.sendPathResponse = pathResponseExpanded } else { c.path.sendPathResponse = pathResponseSmall } c.path.data = data } func (c *Conn) handlePathResponse(now time.Time, _ pathChallengeData) { // "If the content of a PATH_RESPONSE frame does not match the content of // a PATH_CHALLENGE frame previously sent by the endpoint, // the endpoint MAY generate a connection error of type PROTOCOL_VIOLATION." // https://www.rfc-editor.org/rfc/rfc9000.html#section-19.18-4 // // We never send PATH_CHALLENGE frames. c.abort(now, localTransportError{ code: errProtocolViolation, reason: "PATH_RESPONSE received when no PATH_CHALLENGE sent", }) } // appendPathFrames appends path validation related frames to the current packet. // If the return value pad is true, then the packet should be padded to 1200 bytes. func (c *Conn) appendPathFrames() (pad, ok bool) { if c.path.sendPathResponse == pathResponseNotNeeded { return pad, true } // We're required to send the PATH_RESPONSE on the path where the // PATH_CHALLENGE was received (RFC 9000, Section 8.2.2). // // At the moment, we don't support path migration and reject packets if // the peer changes its source address, so just sending the PATH_RESPONSE // in a regular datagram is fine. if !c.w.appendPathResponseFrame(c.path.data) { return pad, false } if c.path.sendPathResponse == pathResponseExpanded { pad = true } c.path.sendPathResponse = pathResponseNotNeeded return pad, true }