send PATH_RESPONSEs on the same path (#4991)

* make it possible to pack path probes with multiple frames

* simplify function signature of pathManager.HandlePacket

* simplify connection short header packet handling logic

No functional change expected.

* make server send PATH_RESPONSEs on the same path

This makes sure that we’re actually testing for return routability.
This commit is contained in:
Marten Seemann
2025-03-16 10:28:53 +07:00
committed by GitHub
parent 7e3d668981
commit 6fe46d6253
8 changed files with 281 additions and 152 deletions

View File

@@ -46,47 +46,69 @@ func newPathManager(
// Returns a path challenge frame if one should be sent.
// May return nil.
func (pm *pathManager) HandlePacket(p receivedPacket, isNonProbing bool) (_ protocol.ConnectionID, _ ackhandler.Frame, shouldSwitch bool) {
for _, path := range pm.paths {
if addrsEqual(path.addr, p.remoteAddr) {
func (pm *pathManager) HandlePacket(
remoteAddr net.Addr,
pathChallenge *wire.PathChallengeFrame, // may be nil if the packet didn't contain a PATH_CHALLENGE
isNonProbing bool,
) (_ protocol.ConnectionID, _ []ackhandler.Frame, shouldSwitch bool) {
var p *path
pathID := pm.nextPathID
for id, path := range pm.paths {
if addrsEqual(path.addr, remoteAddr) {
p = path
pathID = id
// already sent a PATH_CHALLENGE for this path
if isNonProbing {
path.rcvdNonProbing = true
}
if pm.logger.Debug() {
pm.logger.Debugf("received packet for path %s that was already probed, validated: %t", p.remoteAddr, path.validated)
pm.logger.Debugf("received packet for path %s that was already probed, validated: %t", remoteAddr, path.validated)
}
shouldSwitch = path.validated && path.rcvdNonProbing
if pathChallenge == nil {
return protocol.ConnectionID{}, nil, shouldSwitch
}
return protocol.ConnectionID{}, ackhandler.Frame{}, path.validated && path.rcvdNonProbing
}
}
if len(pm.paths) >= maxPaths {
if pm.logger.Debug() {
pm.logger.Debugf("received packet for previously unseen path %s, but already have %d paths", p.remoteAddr, len(pm.paths))
pm.logger.Debugf("received packet for previously unseen path %s, but already have %d paths", remoteAddr, len(pm.paths))
}
return protocol.ConnectionID{}, ackhandler.Frame{}, false
return protocol.ConnectionID{}, nil, shouldSwitch
}
// previously unseen path, initiate path validation by sending a PATH_CHALLENGE
connID, ok := pm.getConnID(pm.nextPathID)
connID, ok := pm.getConnID(pathID)
if !ok {
pm.logger.Debugf("skipping validation of new path %s since no connection ID is available", p.remoteAddr)
return protocol.ConnectionID{}, ackhandler.Frame{}, false
pm.logger.Debugf("skipping validation of new path %s since no connection ID is available", remoteAddr)
return protocol.ConnectionID{}, nil, shouldSwitch
}
var b [8]byte
rand.Read(b[:])
pm.paths[pm.nextPathID] = &path{
addr: p.remoteAddr,
pathChallenge: b,
rcvdNonProbing: isNonProbing,
frames := make([]ackhandler.Frame, 0, 2)
if p == nil {
var pathChallengeData [8]byte
rand.Read(pathChallengeData[:])
p = &path{
addr: remoteAddr,
rcvdNonProbing: isNonProbing,
pathChallenge: pathChallengeData,
}
frames = append(frames, ackhandler.Frame{
Frame: &wire.PathChallengeFrame{Data: p.pathChallenge},
Handler: (*pathManagerAckHandler)(pm),
})
pm.paths[pm.nextPathID] = p
pm.nextPathID++
pm.logger.Debugf("enqueueing PATH_CHALLENGE for new path %s", remoteAddr)
}
pm.nextPathID++
frame := ackhandler.Frame{
Frame: &wire.PathChallengeFrame{Data: b},
Handler: (*pathManagerAckHandler)(pm),
if pathChallenge != nil {
frames = append(frames, ackhandler.Frame{
Frame: &wire.PathResponseFrame{Data: pathChallenge.Data},
Handler: (*pathManagerAckHandler)(pm),
})
}
pm.logger.Debugf("enqueueing PATH_CHALLENGE for new path %s", p.remoteAddr)
return connID, frame, false
return connID, frames, shouldSwitch
}
func (pm *pathManager) HandlePathResponseFrame(f *wire.PathResponseFrame) {