| // Copyright 2023 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 ( |
| "bytes" |
| "encoding/binary" |
| "errors" |
| "time" |
| ) |
| |
| func (c *Conn) handleDatagram(now time.Time, dgram *datagram) (handled bool) { |
| if !c.localAddr.IsValid() { |
| // We don't have any way to tell in the general case what address we're |
| // sending packets from. Set our address from the destination address of |
| // the first packet received from the peer. |
| c.localAddr = dgram.localAddr |
| } |
| if dgram.peerAddr.IsValid() && dgram.peerAddr != c.peerAddr { |
| if c.side == clientSide { |
| // "If a client receives packets from an unknown server address, |
| // the client MUST discard these packets." |
| // https://www.rfc-editor.org/rfc/rfc9000#section-9-6 |
| return false |
| } |
| // We currently don't support connection migration, |
| // so for now the server also drops packets from an unknown address. |
| return false |
| } |
| buf := dgram.b |
| c.loss.datagramReceived(now, len(buf)) |
| if c.isDraining() { |
| return false |
| } |
| for len(buf) > 0 { |
| var n int |
| ptype := getPacketType(buf) |
| switch ptype { |
| case packetTypeInitial: |
| if c.side == serverSide && len(dgram.b) < paddedInitialDatagramSize { |
| // Discard client-sent Initial packets in too-short datagrams. |
| // https://www.rfc-editor.org/rfc/rfc9000#section-14.1-4 |
| return false |
| } |
| n = c.handleLongHeader(now, dgram, ptype, initialSpace, c.keysInitial.r, buf) |
| case packetTypeHandshake: |
| n = c.handleLongHeader(now, dgram, ptype, handshakeSpace, c.keysHandshake.r, buf) |
| case packetType1RTT: |
| n = c.handle1RTT(now, dgram, buf) |
| case packetTypeRetry: |
| c.handleRetry(now, buf) |
| return true |
| case packetTypeVersionNegotiation: |
| c.handleVersionNegotiation(now, buf) |
| return true |
| default: |
| n = -1 |
| } |
| if n <= 0 { |
| // We don't expect to get a stateless reset with a valid |
| // destination connection ID, since the sender of a stateless |
| // reset doesn't know what the connection ID is. |
| // |
| // We're required to perform this check anyway. |
| // |
| // "[...] the comparison MUST be performed when the first packet |
| // in an incoming datagram [...] cannot be decrypted." |
| // https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-2 |
| if len(buf) == len(dgram.b) && len(buf) > statelessResetTokenLen { |
| var token statelessResetToken |
| copy(token[:], buf[len(buf)-len(token):]) |
| if c.handleStatelessReset(now, token) { |
| return true |
| } |
| } |
| // Invalid data at the end of a datagram is ignored. |
| return false |
| } |
| c.idleHandlePacketReceived(now) |
| buf = buf[n:] |
| } |
| return true |
| } |
| |
| func (c *Conn) handleLongHeader(now time.Time, dgram *datagram, ptype packetType, space numberSpace, k fixedKeys, buf []byte) int { |
| if !k.isSet() { |
| return skipLongHeaderPacket(buf) |
| } |
| |
| pnumMax := c.acks[space].largestSeen() |
| p, n := parseLongHeaderPacket(buf, k, pnumMax) |
| if n < 0 { |
| return -1 |
| } |
| if buf[0]&reservedLongBits != 0 { |
| // Reserved header bits must be 0. |
| // https://www.rfc-editor.org/rfc/rfc9000#section-17.2-8.2.1 |
| c.abort(now, localTransportError{ |
| code: errProtocolViolation, |
| reason: "reserved header bits are not zero", |
| }) |
| return -1 |
| } |
| if p.version != quicVersion1 { |
| // The peer has changed versions on us mid-handshake? |
| c.abort(now, localTransportError{ |
| code: errProtocolViolation, |
| reason: "protocol version changed during handshake", |
| }) |
| return -1 |
| } |
| |
| if !c.acks[space].shouldProcess(p.num) { |
| return n |
| } |
| |
| if logPackets { |
| logInboundLongPacket(c, p) |
| } |
| if c.logEnabled(QLogLevelPacket) { |
| c.logLongPacketReceived(p, buf[:n]) |
| } |
| c.connIDState.handlePacket(c, p.ptype, p.srcConnID) |
| ackEliciting := c.handleFrames(now, dgram, ptype, space, p.payload) |
| c.acks[space].receive(now, space, p.num, ackEliciting) |
| if p.ptype == packetTypeHandshake && c.side == serverSide { |
| c.loss.validateClientAddress() |
| |
| // "[...] a server MUST discard Initial keys when it first successfully |
| // processes a Handshake packet [...]" |
| // https://www.rfc-editor.org/rfc/rfc9001#section-4.9.1-2 |
| c.discardKeys(now, initialSpace) |
| } |
| return n |
| } |
| |
| func (c *Conn) handle1RTT(now time.Time, dgram *datagram, buf []byte) int { |
| if !c.keysAppData.canRead() { |
| // 1-RTT packets extend to the end of the datagram, |
| // so skip the remainder of the datagram if we can't parse this. |
| return len(buf) |
| } |
| |
| pnumMax := c.acks[appDataSpace].largestSeen() |
| p, err := parse1RTTPacket(buf, &c.keysAppData, connIDLen, pnumMax) |
| if err != nil { |
| // A localTransportError terminates the connection. |
| // Other errors indicate an unparseable packet, but otherwise may be ignored. |
| if _, ok := err.(localTransportError); ok { |
| c.abort(now, err) |
| } |
| return -1 |
| } |
| if buf[0]&reserved1RTTBits != 0 { |
| // Reserved header bits must be 0. |
| // https://www.rfc-editor.org/rfc/rfc9000#section-17.3.1-4.8.1 |
| c.abort(now, localTransportError{ |
| code: errProtocolViolation, |
| reason: "reserved header bits are not zero", |
| }) |
| return -1 |
| } |
| |
| if !c.acks[appDataSpace].shouldProcess(p.num) { |
| return len(buf) |
| } |
| |
| if logPackets { |
| logInboundShortPacket(c, p) |
| } |
| if c.logEnabled(QLogLevelPacket) { |
| c.log1RTTPacketReceived(p, buf) |
| } |
| ackEliciting := c.handleFrames(now, dgram, packetType1RTT, appDataSpace, p.payload) |
| c.acks[appDataSpace].receive(now, appDataSpace, p.num, ackEliciting) |
| return len(buf) |
| } |
| |
| func (c *Conn) handleRetry(now time.Time, pkt []byte) { |
| if c.side != clientSide { |
| return // clients don't send Retry packets |
| } |
| // "After the client has received and processed an Initial or Retry packet |
| // from the server, it MUST discard any subsequent Retry packets that it receives." |
| // https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-1 |
| if !c.keysInitial.canRead() { |
| return // discarded Initial keys, connection is already established |
| } |
| if c.acks[initialSpace].seen.numRanges() != 0 { |
| return // processed at least one packet |
| } |
| if c.retryToken != nil { |
| return // received a Retry already |
| } |
| // "Clients MUST discard Retry packets that have a Retry Integrity Tag |
| // that cannot be validated." |
| // https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-2 |
| p, ok := parseRetryPacket(pkt, c.connIDState.originalDstConnID) |
| if !ok { |
| return |
| } |
| // "A client MUST discard a Retry packet with a zero-length Retry Token field." |
| // https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-2 |
| if len(p.token) == 0 { |
| return |
| } |
| c.retryToken = cloneBytes(p.token) |
| c.connIDState.handleRetryPacket(p.srcConnID) |
| // We need to resend any data we've already sent in Initial packets. |
| // We must not reuse already sent packet numbers. |
| c.loss.discardPackets(initialSpace, c.log, c.handleAckOrLoss) |
| // TODO: Discard 0-RTT packets as well, once we support 0-RTT. |
| } |
| |
| var errVersionNegotiation = errors.New("server does not support QUIC version 1") |
| |
| func (c *Conn) handleVersionNegotiation(now time.Time, pkt []byte) { |
| if c.side != clientSide { |
| return // servers don't handle Version Negotiation packets |
| } |
| // "A client MUST discard any Version Negotiation packet if it has |
| // received and successfully processed any other packet [...]" |
| // https://www.rfc-editor.org/rfc/rfc9000#section-6.2-2 |
| if !c.keysInitial.canRead() { |
| return // discarded Initial keys, connection is already established |
| } |
| if c.acks[initialSpace].seen.numRanges() != 0 { |
| return // processed at least one packet |
| } |
| _, srcConnID, versions := parseVersionNegotiation(pkt) |
| if len(c.connIDState.remote) < 1 || !bytes.Equal(c.connIDState.remote[0].cid, srcConnID) { |
| return // Source Connection ID doesn't match what we sent |
| } |
| for len(versions) >= 4 { |
| ver := binary.BigEndian.Uint32(versions) |
| if ver == 1 { |
| // "A client MUST discard a Version Negotiation packet that lists |
| // the QUIC version selected by the client." |
| // https://www.rfc-editor.org/rfc/rfc9000#section-6.2-2 |
| return |
| } |
| versions = versions[4:] |
| } |
| // "A client that supports only this version of QUIC MUST |
| // abandon the current connection attempt if it receives |
| // a Version Negotiation packet, [with the two exceptions handled above]." |
| // https://www.rfc-editor.org/rfc/rfc9000#section-6.2-2 |
| c.abortImmediately(now, errVersionNegotiation) |
| } |
| |
| func (c *Conn) handleFrames(now time.Time, dgram *datagram, ptype packetType, space numberSpace, payload []byte) (ackEliciting bool) { |
| if len(payload) == 0 { |
| // "An endpoint MUST treat receipt of a packet containing no frames |
| // as a connection error of type PROTOCOL_VIOLATION." |
| // https://www.rfc-editor.org/rfc/rfc9000#section-12.4-3 |
| c.abort(now, localTransportError{ |
| code: errProtocolViolation, |
| reason: "packet contains no frames", |
| }) |
| return false |
| } |
| // frameOK verifies that ptype is one of the packets in mask. |
| frameOK := func(c *Conn, ptype, mask packetType) (ok bool) { |
| if ptype&mask == 0 { |
| // "An endpoint MUST treat receipt of a frame in a packet type |
| // that is not permitted as a connection error of type |
| // PROTOCOL_VIOLATION." |
| // https://www.rfc-editor.org/rfc/rfc9000#section-12.4-3 |
| c.abort(now, localTransportError{ |
| code: errProtocolViolation, |
| reason: "frame not allowed in packet", |
| }) |
| return false |
| } |
| return true |
| } |
| // Packet masks from RFC 9000 Table 3. |
| // https://www.rfc-editor.org/rfc/rfc9000#table-3 |
| const ( |
| IH_1 = packetTypeInitial | packetTypeHandshake | packetType1RTT |
| __01 = packetType0RTT | packetType1RTT |
| ___1 = packetType1RTT |
| ) |
| for len(payload) > 0 { |
| switch payload[0] { |
| case frameTypePadding, frameTypeAck, frameTypeAckECN, |
| frameTypeConnectionCloseTransport, frameTypeConnectionCloseApplication: |
| default: |
| ackEliciting = true |
| } |
| n := -1 |
| switch payload[0] { |
| case frameTypePadding: |
| // PADDING is OK in all spaces. |
| n = 1 |
| case frameTypePing: |
| // PING is OK in all spaces. |
| // |
| // A PING frame causes us to respond with an ACK by virtue of being |
| // an ack-eliciting frame, but requires no other action. |
| n = 1 |
| case frameTypeAck, frameTypeAckECN: |
| if !frameOK(c, ptype, IH_1) { |
| return |
| } |
| n = c.handleAckFrame(now, space, payload) |
| case frameTypeResetStream: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| n = c.handleResetStreamFrame(now, space, payload) |
| case frameTypeStopSending: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| n = c.handleStopSendingFrame(now, space, payload) |
| case frameTypeCrypto: |
| if !frameOK(c, ptype, IH_1) { |
| return |
| } |
| n = c.handleCryptoFrame(now, space, payload) |
| case frameTypeNewToken: |
| if !frameOK(c, ptype, ___1) { |
| return |
| } |
| _, n = consumeNewTokenFrame(payload) |
| case 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f: // STREAM |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| n = c.handleStreamFrame(now, space, payload) |
| case frameTypeMaxData: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| n = c.handleMaxDataFrame(now, payload) |
| case frameTypeMaxStreamData: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| n = c.handleMaxStreamDataFrame(now, payload) |
| case frameTypeMaxStreamsBidi, frameTypeMaxStreamsUni: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| n = c.handleMaxStreamsFrame(now, payload) |
| case frameTypeDataBlocked: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| _, n = consumeDataBlockedFrame(payload) |
| case frameTypeStreamsBlockedBidi, frameTypeStreamsBlockedUni: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| _, _, n = consumeStreamsBlockedFrame(payload) |
| case frameTypeStreamDataBlocked: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| _, _, n = consumeStreamDataBlockedFrame(payload) |
| case frameTypeNewConnectionID: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| n = c.handleNewConnectionIDFrame(now, space, payload) |
| case frameTypeRetireConnectionID: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| n = c.handleRetireConnectionIDFrame(now, space, payload) |
| case frameTypePathChallenge: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| n = c.handlePathChallengeFrame(now, dgram, space, payload) |
| case frameTypePathResponse: |
| if !frameOK(c, ptype, ___1) { |
| return |
| } |
| n = c.handlePathResponseFrame(now, space, payload) |
| case frameTypeConnectionCloseTransport: |
| // Transport CONNECTION_CLOSE is OK in all spaces. |
| n = c.handleConnectionCloseTransportFrame(now, payload) |
| case frameTypeConnectionCloseApplication: |
| if !frameOK(c, ptype, __01) { |
| return |
| } |
| n = c.handleConnectionCloseApplicationFrame(now, payload) |
| case frameTypeHandshakeDone: |
| if !frameOK(c, ptype, ___1) { |
| return |
| } |
| n = c.handleHandshakeDoneFrame(now, space, payload) |
| } |
| if n < 0 { |
| c.abort(now, localTransportError{ |
| code: errFrameEncoding, |
| reason: "frame encoding error", |
| }) |
| return false |
| } |
| payload = payload[n:] |
| } |
| return ackEliciting |
| } |
| |
| func (c *Conn) handleAckFrame(now time.Time, space numberSpace, payload []byte) int { |
| c.loss.receiveAckStart() |
| largest, ackDelay, n := consumeAckFrame(payload, func(rangeIndex int, start, end packetNumber) { |
| if end > c.loss.nextNumber(space) { |
| // Acknowledgement of a packet we never sent. |
| c.abort(now, localTransportError{ |
| code: errProtocolViolation, |
| reason: "acknowledgement for unsent packet", |
| }) |
| return |
| } |
| c.loss.receiveAckRange(now, space, rangeIndex, start, end, c.handleAckOrLoss) |
| }) |
| // Prior to receiving the peer's transport parameters, we cannot |
| // interpret the ACK Delay field because we don't know the ack_delay_exponent |
| // to apply. |
| // |
| // For servers, we should always know the ack_delay_exponent because the |
| // client's transport parameters are carried in its Initial packets and we |
| // won't send an ack-eliciting Initial packet until after receiving the last |
| // client Initial packet. |
| // |
| // For clients, we won't receive the server's transport parameters until handling |
| // its Handshake flight, which will probably happen after reading its ACK for our |
| // Initial packet(s). However, the peer's acknowledgement delay cannot reduce our |
| // adjusted RTT sample below min_rtt, and min_rtt is generally going to be set |
| // by the packet containing the ACK for our Initial flight. Therefore, the |
| // ACK Delay for an ACK in the Initial space is likely to be ignored anyway. |
| // |
| // Long story short, setting the delay to 0 prior to reading transport parameters |
| // is usually going to have no effect, will have only a minor effect in the rare |
| // cases when it happens, and there aren't any good alternatives anyway since we |
| // can't interpret the ACK Delay field without knowing the exponent. |
| var delay time.Duration |
| if c.peerAckDelayExponent >= 0 { |
| delay = ackDelay.Duration(uint8(c.peerAckDelayExponent)) |
| } |
| c.loss.receiveAckEnd(now, c.log, space, delay, c.handleAckOrLoss) |
| if space == appDataSpace { |
| c.keysAppData.handleAckFor(largest) |
| } |
| return n |
| } |
| |
| func (c *Conn) handleMaxDataFrame(now time.Time, payload []byte) int { |
| maxData, n := consumeMaxDataFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| c.streams.outflow.setMaxData(maxData) |
| return n |
| } |
| |
| func (c *Conn) handleMaxStreamDataFrame(now time.Time, payload []byte) int { |
| id, maxStreamData, n := consumeMaxStreamDataFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| if s := c.streamForFrame(now, id, sendStream); s != nil { |
| if err := s.handleMaxStreamData(maxStreamData); err != nil { |
| c.abort(now, err) |
| return -1 |
| } |
| } |
| return n |
| } |
| |
| func (c *Conn) handleMaxStreamsFrame(now time.Time, payload []byte) int { |
| styp, max, n := consumeMaxStreamsFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| c.streams.localLimit[styp].setMax(max) |
| return n |
| } |
| |
| func (c *Conn) handleResetStreamFrame(now time.Time, space numberSpace, payload []byte) int { |
| id, code, finalSize, n := consumeResetStreamFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| if s := c.streamForFrame(now, id, recvStream); s != nil { |
| if err := s.handleReset(code, finalSize); err != nil { |
| c.abort(now, err) |
| } |
| } |
| return n |
| } |
| |
| func (c *Conn) handleStopSendingFrame(now time.Time, space numberSpace, payload []byte) int { |
| id, code, n := consumeStopSendingFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| if s := c.streamForFrame(now, id, sendStream); s != nil { |
| if err := s.handleStopSending(code); err != nil { |
| c.abort(now, err) |
| } |
| } |
| return n |
| } |
| |
| func (c *Conn) handleCryptoFrame(now time.Time, space numberSpace, payload []byte) int { |
| off, data, n := consumeCryptoFrame(payload) |
| err := c.handleCrypto(now, space, off, data) |
| if err != nil { |
| c.abort(now, err) |
| return -1 |
| } |
| return n |
| } |
| |
| func (c *Conn) handleStreamFrame(now time.Time, space numberSpace, payload []byte) int { |
| id, off, fin, b, n := consumeStreamFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| if s := c.streamForFrame(now, id, recvStream); s != nil { |
| if err := s.handleData(off, b, fin); err != nil { |
| c.abort(now, err) |
| } |
| } |
| return n |
| } |
| |
| func (c *Conn) handleNewConnectionIDFrame(now time.Time, space numberSpace, payload []byte) int { |
| seq, retire, connID, resetToken, n := consumeNewConnectionIDFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| if err := c.connIDState.handleNewConnID(c, seq, retire, connID, resetToken); err != nil { |
| c.abort(now, err) |
| } |
| return n |
| } |
| |
| func (c *Conn) handleRetireConnectionIDFrame(now time.Time, space numberSpace, payload []byte) int { |
| seq, n := consumeRetireConnectionIDFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| if err := c.connIDState.handleRetireConnID(c, seq); err != nil { |
| c.abort(now, err) |
| } |
| return n |
| } |
| |
| func (c *Conn) handlePathChallengeFrame(now time.Time, dgram *datagram, space numberSpace, payload []byte) int { |
| data, n := consumePathChallengeFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| c.handlePathChallenge(now, dgram, data) |
| return n |
| } |
| |
| func (c *Conn) handlePathResponseFrame(now time.Time, space numberSpace, payload []byte) int { |
| data, n := consumePathResponseFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| c.handlePathResponse(now, data) |
| return n |
| } |
| |
| func (c *Conn) handleConnectionCloseTransportFrame(now time.Time, payload []byte) int { |
| code, _, reason, n := consumeConnectionCloseTransportFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| c.handlePeerConnectionClose(now, peerTransportError{code: code, reason: reason}) |
| return n |
| } |
| |
| func (c *Conn) handleConnectionCloseApplicationFrame(now time.Time, payload []byte) int { |
| code, reason, n := consumeConnectionCloseApplicationFrame(payload) |
| if n < 0 { |
| return -1 |
| } |
| c.handlePeerConnectionClose(now, &ApplicationError{Code: code, Reason: reason}) |
| return n |
| } |
| |
| func (c *Conn) handleHandshakeDoneFrame(now time.Time, space numberSpace, payload []byte) int { |
| if c.side == serverSide { |
| // Clients should never send HANDSHAKE_DONE. |
| // https://www.rfc-editor.org/rfc/rfc9000#section-19.20-4 |
| c.abort(now, localTransportError{ |
| code: errProtocolViolation, |
| reason: "client sent HANDSHAKE_DONE", |
| }) |
| return -1 |
| } |
| if c.isAlive() { |
| c.confirmHandshake(now) |
| } |
| return 1 |
| } |
| |
| var errStatelessReset = errors.New("received stateless reset") |
| |
| func (c *Conn) handleStatelessReset(now time.Time, resetToken statelessResetToken) (valid bool) { |
| if !c.connIDState.isValidStatelessResetToken(resetToken) { |
| return false |
| } |
| c.setFinalError(errStatelessReset) |
| c.enterDraining(now) |
| return true |
| } |