package quicimport ()// This file is the X1 Stage 5f send-side multipath orchestration// (draft-ietf-quic-multipath): opening a second PathID, validating it with a// PATH_CHALLENGE/PATH_RESPONSE exchange, and scheduling 1-RTT sends over it.//// It is deliberately separate from path_manager_outgoing.go, which is the RFC// 9000 single-path connection-MIGRATION manager (the int64 pathID concept that// switches the active 4-tuple and discards the old one). Multipath keeps BOTH// paths alive: PathIDZero never stops carrying data, and a non-zero path is an// additional, independent number space + congestion controller (5a) addressed// by its own connection IDs (5c). Nothing here touches pathManagerOutgoing.//// Threading: every field of multipathOutgoing is owned by the connection's run// goroutine. The sentPacketHandler / receivedPacketHandler / connIDGenerator// have no locks and are read by the packer on every 1-RTT packet, so opening a// path from an application goroutine would race them (confirmed with -race).// Conn.OpenPath therefore only enqueues an openPathRequest; the run loop// performs the actual provisioning in processOpenPathRequests.// ErrPathLimit is returned by OpenPath when the peer has not yet advertised a// large enough MAX_PATH_ID, or when the requested path would exceed the local// limit. The condition can be transient immediately after handshake completion,// while the peer's MAX_PATH_ID frame is still in flight.varErrPathLimit = errors.New("quic: path limit prevents opening path")// pathOpenState tracks the local lifecycle of one non-zero multipath PathID.// It mirrors the recovery-irrelevant subset of reference/paths.rs that the// initiator drives: sending PATH_CHALLENGEs (on_path_challenges_unconfirmed,// paths.rs:185) until a PATH_RESPONSE validates the path (validated,// paths.rs:200), then reporting it (open_status, paths.rs:273).type pathOpenState struct { id protocol.PathID// challenges holds the PATH_CHALLENGE tokens we have sent on this path and // not yet seen validated. A PATH_RESPONSE carrying any of them validates the // path (paths.rs:497-527: a response to any sent challenge validates). challenges [][8]byte// challengeSent is set once we have emitted at least one PATH_CHALLENGE, so // driveMultipath does not re-send on every run-loop iteration. challengeSent bool// validated is set when a matching PATH_RESPONSE has been received. Until // then the path carries only its PATH_CHALLENGE; application data waits for // validation (RFC 9000 ยง8: do not send non-probing data on an unvalidated // path). It corresponds to PathData::validated (paths.rs:200). validated bool// validatedChan is closed when validated flips to true, so Conn.OpenPath // (running on the application goroutine) can block until the run loop // reports the path usable. validatedChan chanstruct{}// pendingResponses holds PATH_RESPONSE tokens we owe the peer for // PATH_CHALLENGEs it sent on this path. driveMultipath flushes them. pendingResponses [][8]byte// sendData is the per-path application send queue: DATAGRAM payloads the // application asked to send specifically over this path // (MultipathPath.SendDatagram). Keeping it per-path (rather than reusing the // connection datagram queue) is what makes "this datagram rode path N" // deterministic, and leaves the path-0 send loop byte-identical. It is // drained only in the run goroutine. sendData [][]byte// qntRoute is the validated remote address for a QNT-opened path. Packets // for this path are sent to this address instead of the connection's // original remote address. qntRoute netip.AddrPort// qntUDPAddr is the allocation-bearing net representation of qntRoute. The // route is immutable after path creation, so convert it once rather than on // every pass through the send loop. qntUDPAddr *net.UDPAddr// cidBlockedSent is set after we ask the peer for a path connection ID. cidBlockedSent bool}// openPathRequest is the command Conn.OpenPath hands to the run goroutine. The// run loop fills in result/err and closes done.type openPathRequest struct { pid protocol.PathID done chanstruct{} err error path *MultipathPath}// multipathOutgoing is the run-goroutine-owned send-side multipath state.type multipathOutgoing struct { paths map[protocol.PathID]*pathOpenState// nextPathID is the PathID to assign to the next opened path. PathIDZero is // the always-present initial path, so non-zero paths start at 1. nextPathID protocol.PathID// migratedRemote is the direct route the ordinary (path-0) send conn has been // migrated onto after a QNT route was validated+selected. Zero until the first // migration; guards processQNTValidatedPathOpen against re-migrating every // round. Ordinary stream frames egress here, not the relay-mapped remote. migratedRemote netip.AddrPort// premigrationRemote is the ordinary send remote captured before the first // QNT migration. For the relay-first path this is the relay-mapped address; // for direct-first dials Endpoint may seed a relay fallback explicitly. premigrationRemote net.Addr// revertedRoute / revertedRouteUntil impose a cooldown on re-migrating to // a route that was recently reverted. A fresh QNT validation is evidence a // route came back (the challenge round-tripped on the exact 4-tuple), but // validated candidates from the same round can arrive seconds apart, and // re-migrating into a link that is still flapping would churn the // connection through repeated congestion resets. After the cooldown a // freshly validated route is trusted again, so a transient flap does not // forfeit the direct path for the connection's lifetime. revertedRoute netip.AddrPort revertedRouteUntil monotime.Time}// qntRemigrationCooldown is how long after a revert a reverted route is// refused re-migration. QNT re-validates routes on the holepunch upgrade// cadence, so one cooldown window typically spans a full validation round.const qntRemigrationCooldown = 30 * time.Secondfunc newMultipathOutgoing() *multipathOutgoing {return &multipathOutgoing{paths: make(map[protocol.PathID]*pathOpenState),nextPathID: 1, }}// queuePathResponse records a PATH_RESPONSE owed on path pid. The path may not// be provisioned yet if this is racing the lazy join; the response is dropped// in that case, and the peer will re-send its PATH_CHALLENGE.func ( *multipathOutgoing) ( protocol.PathID, [8]byte) { , := .paths[]if ! {return } .pendingResponses = append(.pendingResponses, )}// MultipathPath is the application handle to a non-zero multipath PathID. It is// returned by Conn.OpenPath. Validated blocks until the path completes its// PATH_CHALLENGE/PATH_RESPONSE validation, at which point 1-RTT packets// (including application data) are scheduled over it by the run loop.typeMultipathPathstruct { conn *Conn id protocol.PathID validated chanstruct{}}// PathID returns the draft-multipath PathID of this path.func ( *MultipathPath) () protocol.PathID { return .id }// Validated blocks until the path has been validated (a PATH_RESPONSE to our// PATH_CHALLENGE arrived) or ctx is done / the connection closed.func ( *MultipathPath) ( context.Context) error {select {case<-.validated:returnnilcase<-.conn.ctx.Done():returncontext.Cause(.conn.ctx)case<-.Done():returncontext.Cause() }}// OpenPath opens a second QUIC multipath path (draft-ietf-quic-multipath) over// the connection's existing socket, distinguished from PathIDZero by its own// connection IDs rather than its 4-tuple. It is the draft-multipath path-open,// NOT the RFC 9000 single-path migration AddPath: both paths stay alive.//// OpenPath requires multipath to have been negotiated and the handshake to be// confirmed (PATH_NEW_CONNECTION_ID / PATH_CHALLENGE are 1-RTT-only). It hands// the request to the run goroutine, which provisions the per-path send/recv// state, issues a path connection ID, and begins the PATH_CHALLENGE validation.// The returned MultipathPath.Validated blocks until the path is usable.//// tr is accepted for API parity with AddPath and future multi-socket paths; in// this build the second path shares the connection's socket (the peer demuxes// by connection ID), so tr may be nil.func ( *Conn) ( *Transport) (*MultipathPath, error) {if !.multipathNegotiated() {returnnil, errors.New("quic: multipath not negotiated") } := &openPathRequest{done: make(chanstruct{})}select {case .openPathQueue<- :case<-.ctx.Done():returnnil, .ctx.Err() } .scheduleSending()select {case<-.done:return .path, .errcase<-.ctx.Done():returnnil, .ctx.Err() }}// PathAcksReceived returns the number of PATH_ACK / PATH_ACK_ECN frames this// connection has received for its non-zero multipath paths. It is safe to call// from any goroutine and is used to confirm a second path's packets were// acknowledged (driving that path's bytes-in-flight down).func ( *Conn) () uint64 { return .pathAcksReceived.Load() }// LastPathAckID returns the PathID carried by the most recently received// PATH_ACK / PATH_ACK_ECN frame and whether any such frame has been received.// It lets a test confirm an acknowledgement arrived for a specific non-zero// path. It is safe to call from any goroutine.func ( *Conn) () (protocol.PathID, bool) { := .lastPathAckID.Load()if == 0 {returnprotocol.PathIDZero, false }returnprotocol.PathID( - 1), true}// pathStatsRequest is the command Conn.PathStats hands to the run goroutine,// which fills in stats/ok and closes done.type pathStatsRequest struct { pid protocol.PathID stats ackhandler.PathDebugStats ok bool done chanstruct{}}// PathStats returns the live application-data recovery snapshot for the// multipath PathID pid (its own number space + congestion controller). It is a// test-support hook: the query runs on the connection's run goroutine โ the// only goroutine permitted to read the lock-free sentPacketHandler โ so it is// race-free. ok is false if pid is not an open path or the connection closed.func ( *Conn) ( protocol.PathID) (ackhandler.PathDebugStats, bool) { := &pathStatsRequest{pid: , done: make(chanstruct{})}select {case .pathStatsQueue<- :case<-.ctx.Done():returnackhandler.PathDebugStats{}, false } .scheduleSending()select {case<-.done:return .stats, .okcase<-.ctx.Done():returnackhandler.PathDebugStats{}, false }}// PathInfo is a snapshot of one qng multipath path's application-facing state.//// Path 0 is the initial path and is not listed here. The entries returned by// [Conn.Paths] are real qng non-zero paths provisioned by OpenPath or by a peer// packet that caused lazy path join. RemoteAddr is set only when qng has an// address that actually routes the path, currently for QNT-opened paths.typePathInfostruct {// ID is the QUIC multipath PathID. ID protocol.PathID// Validated reports whether the path completed PATH_CHALLENGE / // PATH_RESPONSE validation and can carry non-probing application data. Validated bool// RemoteAddr is the remote UDP route for this path, when known. RemoteAddr netip.AddrPort// SmoothedRTT is the path's application-data RTT estimate, when HasRTT is // true. SmoothedRTT time.Duration// HasRTT reports whether SmoothedRTT was observed for this path. HasRTT bool// BytesInFlight is the path's current application-data bytes in flight, // when HasBytesInFlight is true. BytesInFlight protocol.ByteCount// HasBytesInFlight reports whether BytesInFlight was observed for this path. HasBytesInFlight bool// BytesSent is the cumulative 1-RTT/0-RTT application-data packet // bytes sent on this path, when HasBytesSent is true. It excludes // Initial/Handshake packets and UDP framing overhead. BytesSent uint64// HasBytesSent reports whether BytesSent was observed for this path. HasBytesSent bool// BytesReceived is the cumulative 1-RTT/0-RTT application-data packet // bytes received on this path, when HasBytesReceived is true. It excludes // Initial/Handshake packets and UDP framing overhead. BytesReceived uint64// HasBytesReceived reports whether BytesReceived was observed for this path. HasBytesReceived bool// CongestionWindow is the path's current congestion window, when // HasCongestionWindow is true. CongestionWindow protocol.ByteCount// HasCongestionWindow reports whether CongestionWindow was observed for this // path. HasCongestionWindow bool// LostPackets is the number of application-data packets declared lost on // this path, when HasLoss is true. LostPackets uint64// LostBytes is the number of application-data bytes declared lost on this // path, when HasLoss is true. LostBytes uint64// HasLoss reports whether LostPackets and LostBytes were observed for this // path. HasLoss bool}// pathSnapshotRequest is the command Conn.Paths hands to the run goroutine,// which fills in paths and closes done.type pathSnapshotRequest struct { performance performanceSnapshotRequest paths []PathInfo done chanstruct{}}// Paths returns a snapshot of this connection's non-zero qng multipath paths.// The query runs on the connection's run goroutine because the path-open state// is owned there. It returns nil if no non-zero path has been opened or the// connection is closed.func ( *Conn) () []PathInfo { := &pathSnapshotRequest{done: make(chanstruct{})}select {case .pathSnapshotQueue<- :case<-.ctx.Done():returnnil } .scheduleSending()select {case<-.done:return .pathscase<-.ctx.Done():returnnil }}// SetMigrationFallbackRemote records a fallback remote for QNT active// migration. It is used when the connection was established directly but the// caller also knows a relay route for the peer.func ( *Conn) ( net.Addr) {if == nil {return } := &setMigrationFallbackRequest{addr: , done: make(chanstruct{})}select {case .setMigrationFallbackQueue<- :case<-.ctx.Done():return } .scheduleSending()select {case<-.done:case<-.ctx.Done(): }}type setMigrationFallbackRequest struct { addr net.Addr done chanstruct{}}func ( *Conn) () {for {select {case := <-.setMigrationFallbackQueue:if .multipathOut == nil { .multipathOut = newMultipathOutgoing() }if .multipathOut.premigrationRemote == nil || (.conn != nil && addrsEqual(.multipathOut.premigrationRemote, .conn.RemoteAddr())) { .multipathOut.premigrationRemote = .addr }close(.done)default:return } }}// processPathSnapshotRequests answers pending Paths queries. Run goroutine// only (called from the run loop), so reading multipathOut is safe.func ( *Conn) () {for {select {case := <-.pathSnapshotQueue: .performance.fill(&.performance)if .multipathOut != nil && len(.multipathOut.paths) > 0 { .paths = make([]PathInfo, 0, len(.multipathOut.paths))for , := range .multipathOut.paths { := PathInfo{ID: ,Validated: .validated,RemoteAddr: .qntRoute, }if , := .sentPacketHandler.PathDebugStats(); {if .HasRTT { .SmoothedRTT = .SmoothedRTT .HasRTT = true } .BytesInFlight = .BytesInFlight .HasBytesInFlight = true .BytesSent = .BytesSent .HasBytesSent = true .BytesReceived = .BytesReceived .HasBytesReceived = true .CongestionWindow = .CongestionWindow .HasCongestionWindow = true .LostPackets = .LostPackets .LostBytes = .LostBytes .HasLoss = true } .paths = append(.paths, ) }sort.Slice(.paths, func(, int) bool {return .paths[].ID < .paths[].ID }) }close(.done)default:return } }}// processPathStatsRequests answers pending PathStats queries. Run goroutine// only (called from the run loop), so reading the sentPacketHandler is safe.func ( *Conn) () {for {select {case := <-.pathStatsQueue: .stats, .ok = .sentPacketHandler.PathDebugStats(.pid)close(.done)default:return } }}// processOpenPathRequests drains pending OpenPath requests. It runs in the run// goroutine, so it can safely provision the (unlocked) sentPacketHandler /// receivedPacketHandler / connIDGenerator state for the new path.func ( *Conn) () error {for {select {case := <-.openPathQueue: .path, .err = .openPathLocked()close(.done)default:returnnil } }}// processQNTValidatedPathOpen consumes at most one validated QNT candidate and// provisions a route-bearing multipath path for it. Run goroutine only.//// A QNT route provisioned here carries per-path DATAGRAM sends, but ordinary// QUIC stream frames egress through the connection's path-0 send conn, which// still targets the relay-mapped remote set at establishment. On the client,// once a direct route is validated+selected we also migrate the ordinary send// conn onto it (RFC 9000 ยง9 connection migration): change the send remote to// the direct 4-tuple and reset MTU for the new path. This makes stream payload// follow the selected direct path instead of staying on relay. The server then// observes app data on the direct path and completes its existing passive// migration.func ( *Conn) ( monotime.Time) error { , , , := .qntOpenValidatedPathLocked()iferrors.Is(, ErrPathLimit) {returnnil }if != nil {return }if && .perspective == protocol.PerspectiveClient { .migrateOrdinarySendToQNTRoute(, ) .scheduleSending() }returnnil}// migrateOrdinarySendToQNTRoute points the connection's path-0 send conn at a// validated direct route so ordinary stream frames egress there. It mirrors the// server-side passive-migration block (handlePathChallenge) and runs at most// once per route, in the run goroutine.func ( *Conn) ( netip.AddrPort, monotime.Time) {// Migrating the ordinary send conn touches the send-side recovery state and // the live send conn, which only exist once the handshake is confirmed. The // same gate guards driveMultipath. A QNT route only validates well after the // handshake in practice, so this never skips a real migration.if !.handshakeConfirmed || .conn == nil {return }if !.IsValid() || .Port() == 0 {return }if := .multipathOut; != nil {if .migratedRemote == || (.revertedRoute == && .Before(.revertedRouteUntil)) {return } } := protocol.ByteCount(.config.InitialPacketSize) := protocol.ByteCount(protocol.MaxPacketBufferSize)if := .peerParams.Load(); .MaxUDPPayloadSize > 0 && .MaxUDPPayloadSize < { = .MaxUDPPayloadSize } .sentPacketHandler.MigratedPath(, ) .currentMTUEstimate.Store(uint32(estimateMaxPayloadSize())) .mtuDiscoverer.Reset(, , )if .multipathOut != nil && .multipathOut.premigrationRemote == nil { .multipathOut.premigrationRemote = .conn.RemoteAddr() } .conn.ChangeRemoteAddr(net.UDPAddrFromAddrPort(), packetInfo{})// Send one ordinary non-probing frame on the migrated direct remote before // application streams depend on it. The peer's existing RFC 9000 passive // migration path switches its return address only after observing non-probing // traffic on the new 4-tuple. .framer.QueueControlFrame(&wire.PingFrame{})if .multipathOut != nil { .multipathOut.migratedRemote = }}func ( *Conn) ( monotime.Time) {const = 3 := .multipathOutif == nil || !.migratedRemote.IsValid() || .premigrationRemote == nil {return }if .sentPacketHandler.PTOCount() < {return } .revertQNTMigration()}func ( *Conn) ( monotime.Time) { := .qntMigrationFallbackDeadline()if .IsZero() || .Before() {return } .revertQNTMigration()}func ( *Conn) () monotime.Time { := .multipathOutif == nil || !.migratedRemote.IsValid() || .premigrationRemote == nil {return0 } := max(5*time.Second, .rttStats.PTO(true)*3)return .lastPacketReceivedTime.Add()}func ( *Conn) ( monotime.Time) { := .multipathOutif == nil || !.migratedRemote.IsValid() || .premigrationRemote == nil {return } := protocol.ByteCount(.config.InitialPacketSize) := protocol.ByteCount(protocol.MaxPacketBufferSize)if := .peerParams.Load(); .MaxUDPPayloadSize > 0 && .MaxUDPPayloadSize < { = .MaxUDPPayloadSize } .sentPacketHandler.MigratedPath(, ) .currentMTUEstimate.Store(uint32(estimateMaxPayloadSize())) .mtuDiscoverer.Reset(, , ) .conn.ChangeRemoteAddr(.premigrationRemote, packetInfo{}) .revertedRoute = .migratedRemote .revertedRouteUntil = .Add(qntRemigrationCooldown) .migratedRemote = netip.AddrPort{} .scheduleSending()}// openPathLocked provisions a new non-zero path. Invariant: run goroutine only.func ( *Conn) () (*MultipathPath, error) {if .multipathOut == nil { .multipathOut = newMultipathOutgoing() } := .multipathOut.nextPathIDif !.canOpenPath() {returnnil, fmt.Errorf("%w: path %d peer max path id not raised that far", ErrPathLimit, ) } .multipathOut.nextPathID++if := .allocatePathLocked(); != nil {returnnil, } := &pathOpenState{id: , validatedChan: make(chanstruct{})} .multipathOut.paths[] = .scheduleSending() // wake the loop to send the first PATH_CHALLENGEreturn &MultipathPath{conn: , id: , validated: .validatedChan}, nil}func ( *Conn) () (protocol.PathID, netip.AddrPort, bool, error) {if , := .qntPeekValidatedProbe(); ! {returnprotocol.PathIDZero, netip.AddrPort{}, false, nil }if .multipathOut == nil { .multipathOut = newMultipathOutgoing() } := .multipathOut.nextPathIDif !.canOpenPath() {returnprotocol.PathIDZero, netip.AddrPort{}, false, fmt.Errorf("%w: path %d peer max path id not raised that far", ErrPathLimit, ) }if := .allocatePathLocked(); != nil {returnprotocol.PathIDZero, netip.AddrPort{}, false, } , := .qntPopValidatedProbe()if ! {returnprotocol.PathIDZero, netip.AddrPort{}, false, nil } .multipathOut.nextPathID++ := &pathOpenState{id: ,validated: true,validatedChan: make(chanstruct{}),qntRoute: ,qntUDPAddr: qntProbeUDPAddr(), }close(.validatedChan) .multipathOut.paths[] = return , , true, nil}func ( *Conn) ( protocol.PathID) error {// Per-path send + receive recovery state (5a / 5e). Each gets its own // independent packet-number space.if := .sentPacketHandler.AddPath(); != nil {returnfmt.Errorf("quic: add send path %d: %w", , ) }if := .receivedPacketHandler.AddPath(, .logger); != nil { .sentPacketHandler.RemovePath()returnfmt.Errorf("quic: add recv path %d: %w", , ) }// Issue one of our connection IDs for this path (5c), so the peer can address // packets to it. This queues a PATH_NEW_CONNECTION_ID (0x3e78) on path 0.if , := .issuePathConnID(); != nil { .receivedPacketHandler.RemovePath() .sentPacketHandler.RemovePath()returnfmt.Errorf("quic: issue path %d connection id: %w", , ) }returnnil}// driveMultipath performs the per-path send work for every open non-zero path.// It runs in the run goroutine after the ordinary path-0 send. For each path it// either (a) sends a PATH_CHALLENGE if the path is still unvalidated and we have// its DCID, or (b) packs a 1-RTT packet (PATH_ACK + any pending data) once the// path is validated. It returns after sending at most one datagram per path so// the run loop keeps cycling through receives.func ( *Conn) ( monotime.Time) error {if .multipathOut == nil || !.handshakeConfirmed {returnnil }for , := range .multipathOut.paths { , := .destConnIDForPath()if ! {// The peer has not issued a PATH_NEW_CONNECTION_ID for this path yet; // ask once and try again after it responds.if !.cidBlockedSent { .queueControlFrame(&wire.PathCIDsBlockedFrame{PathID: , NextSeq: 0}) .cidBlockedSent = true }continue }// Flush any PATH_RESPONSEs we owe on this path first, so the peer can // validate it promptly.iflen(.pendingResponses) > 0 { := make([]ackhandler.Frame, 0, len(.pendingResponses))for , := range .pendingResponses { = append(, ackhandler.Frame{Frame: &wire.PathResponseFrame{Data: }}) } .pendingResponses = nilif := .sendPathPacket(, , , , ); != nil {return } }if !.validated {if := .sendPathChallenge(, , , ); != nil {return }continue }if := .sendOnPath(, , ); != nil {return } }returnnil}// pathDatagram is a DATAGRAM payload destined for a specific multipath PathID,// carried from MultipathPath.SendDatagram into the run goroutine.type pathDatagram struct { pid protocol.PathID data []byte}// SendDatagram queues a DATAGRAM to be sent specifically over this multipath// path. The datagram is delivered to the peer's ordinary datagram receive queue// (DATAGRAM frames are not path-scoped on the wire), but it is guaranteed to// ride a 1-RTT packet addressed to this path's connection ID and drawn from this// path's packet-number space โ which is what makes it observable as "data over// PathID n".func ( *MultipathPath) ( []byte) error {return .conn.SendDatagramOnPath(.id, )}// SendDatagramOnPath queues a DATAGRAM to be sent over the multipath PathID pid.// It is the thread-safe entry point both the OpenPath initiator// (MultipathPath.SendDatagram) and the lazily-joined peer use to put data on a// non-zero path: it only touches pathDatagramQueue (a channel), never the// run-goroutine-owned multipathOut, so it is safe to call from any goroutine.// The run loop drops the datagram if pid is not an open path.func ( *Conn) ( protocol.PathID, []byte) error {if == protocol.PathIDZero {returnerrors.New("quic: SendDatagramOnPath requires a non-zero path id") } := make([]byte, len())copy(, )select {case .pathDatagramQueue<-pathDatagram{pid: , data: }: .scheduleSending()returnnilcase<-.ctx.Done():returncontext.Cause(.ctx) }}// drainPathDatagrams moves queued per-path DATAGRAM sends into their path's send// queue. Run goroutine only (called from the run loop alongside// processOpenPathRequests).func ( *Conn) () {if .multipathOut == nil {return }for {select {case := <-.pathDatagramQueue:if , := .multipathOut.paths[.pid]; { .sendData = append(.sendData, .data) }default:return } }}// sendPathChallenge emits a PATH_CHALLENGE on path pid (addressed to the peer's// pid DCID), recording its token for later validation. It mirrors// PathData::record_path_challenge_sent (paths.rs:436-447). It sends at most one// challenge per path until a response arrives or the path is re-armed.func ( *Conn) ( protocol.PathID, protocol.ConnectionID, *pathOpenState, monotime.Time) error {if .challengeSent {returnnil } := .sentPacketHandler.SendModeForPath(, )if != ackhandler.SendAny && != ackhandler.SendAck {returnnil }var [8]byteif , := rand.Read([:]); != nil {return } .challenges = append(.challenges, ) .challengeSent = true := ackhandler.Frame{Frame: &wire.PathChallengeFrame{Data: }}return .sendPathPacket(, , , []ackhandler.Frame{}, )}// sendOnPath packs and sends one 1-RTT packet targeting the validated path pid:// a PATH_ACK{pid} for what we received plus that path's pending DATAGRAM// payloads (its own send queue). It is a no-op (no datagram sent) when there is// nothing to pack. Application data riding pid carries its bytes-in-flight on// pid's own controller, so it is driven down by the peer's PATH_ACK{pid}.func ( *Conn) ( protocol.PathID, *pathOpenState, monotime.Time) error {ifhasInvalidQNTRoute() {returnnil }if .pathSendQueueBlocked() {returnnil } := .sentPacketHandler.SendModeForPath(, )if != ackhandler.SendAny && != ackhandler.SendAck {returnnil }// Only feed application datagrams once the congestion controller permits new // data; on SendAck we still emit the PATH_ACK but hold the data.var [][]byteif == ackhandler.SendAny { = .sendData } := getPacketBuffer() := .multipathECNMode() , , := .packer.AppendPacketForPath(, , , .maxPacketSize(), , .version)if != nil { .Release()if == errNothingToPack {returnnil }return }// Drop exactly the datagrams that were packed into this packet; any that did // not fit stay queued for the next packet.if > 0 { .sendData = .sendData[:]iflen(.sendData) == 0 { .sendData = nil } } .logShortHeaderPacket(, , .Len()) .registerPackedShortHeaderPacket(, , ) .sendPathBuffer(, , )// If there is more queued data on this path, keep the loop cycling.iflen(.sendData) > 0 { .scheduleSending() }returnnil}// sendPathPacket packs the given frames into a single 1-RTT packet on path pid// (its DCID + its packet number) and sends it. Used for PATH_CHALLENGE, which// must not be coalesced with path-0 data because it has to ride the new path's// connection ID so the peer attributes the validation to pid.func ( *Conn) ( protocol.PathID, protocol.ConnectionID, *pathOpenState, []ackhandler.Frame, monotime.Time) error {ifhasInvalidQNTRoute() {returnnil }if .pathSendQueueBlocked() {returnnil } := getPacketBuffer() := .multipathECNMode() , := .packer.PackPathFramesPacket(, , , , .maxPacketSize(), .version)if != nil { .Release()return } .logShortHeaderPacket(, , .Len()) .registerPackedShortHeaderPacket(, , ) .sendPathBuffer(, , )returnnil}func ( *Conn) ( *pathOpenState) bool {if != nil && .qntRoute.IsValid() {returnfalse }if .sendQueue == nil || !.sendQueue.WouldBlock() {returnfalse } .scheduleSending()returntrue}func ( *Conn) ( *packetBuffer, protocol.ECN, *pathOpenState) {if != nil && .qntRoute.IsValid() { := .qntUDPAddrif == nil { = qntProbeUDPAddr(.qntRoute) .qntUDPAddr = }if == nil { .Release()return } .sendQNTProbeBuffer(, )return } .sendQueue.Send(, 0, )}func hasInvalidQNTRoute( *pathOpenState) bool {return != nil && .qntRoute.IsValid() && !validQNTProbeAddr(.qntRoute)}func ( *Conn) () protocol.ECN {if !.conn.capabilities().ECN {returnprotocol.ECNUnsupported }returnprotocol.ECNNon}// handleMultipathPathResponse checks whether a received PATH_RESPONSE validates// one of our outstanding multipath PATH_CHALLENGEs. A response to any token we// sent on a path validates that path (paths.rs:505-527: validates the path the// challenge was sent on, regardless of the path the response arrived on). It// reports whether the token matched a multipath challenge. Run goroutine only.func ( *Conn) ( *wire.PathResponseFrame) bool {if .multipathOut == nil {returnfalse }for , := range .multipathOut.paths {for , := range .challenges {if == .Data {if !.validated { .validated = true .challenges = nilclose(.validatedChan) .scheduleSending() // start sending data on the now-validated path }returntrue } } }returnfalse}
The pages are generated with Goldsv0.8.4. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.