File: path_manager.go

package info (click to toggle)
golang-github-lucas-clemente-quic-go 0.50.1-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 4,496 kB
  • sloc: sh: 54; makefile: 7
file content (145 lines) | stat: -rw-r--r-- 3,883 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package quic

import (
	"crypto/rand"
	"net"

	"github.com/quic-go/quic-go/internal/ackhandler"
	"github.com/quic-go/quic-go/internal/protocol"
	"github.com/quic-go/quic-go/internal/utils"
	"github.com/quic-go/quic-go/internal/wire"
)

type pathID int64

const maxPaths = 3

type path struct {
	addr           net.Addr
	pathChallenge  [8]byte
	validated      bool
	rcvdNonProbing bool
}

type pathManager struct {
	nextPathID pathID
	paths      map[pathID]*path

	getConnID    func(pathID) (_ protocol.ConnectionID, ok bool)
	retireConnID func(pathID)

	logger utils.Logger
}

func newPathManager(
	getConnID func(pathID) (_ protocol.ConnectionID, ok bool),
	retireConnID func(pathID),
	logger utils.Logger,
) *pathManager {
	return &pathManager{
		paths:        make(map[pathID]*path),
		getConnID:    getConnID,
		retireConnID: retireConnID,
		logger:       logger,
	}
}

// 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) {
			// 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)
			}
			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))
		}
		return protocol.ConnectionID{}, ackhandler.Frame{}, false
	}

	// previously unseen path, initiate path validation by sending a PATH_CHALLENGE
	connID, ok := pm.getConnID(pm.nextPathID)
	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
	}
	var b [8]byte
	rand.Read(b[:])
	pm.paths[pm.nextPathID] = &path{
		addr:           p.remoteAddr,
		pathChallenge:  b,
		rcvdNonProbing: isNonProbing,
	}
	pm.nextPathID++
	frame := ackhandler.Frame{
		Frame:   &wire.PathChallengeFrame{Data: b},
		Handler: (*pathManagerAckHandler)(pm),
	}
	pm.logger.Debugf("enqueueing PATH_CHALLENGE for new path %s", p.remoteAddr)
	return connID, frame, false
}

func (pm *pathManager) HandlePathResponseFrame(f *wire.PathResponseFrame) {
	for _, p := range pm.paths {
		if f.Data == p.pathChallenge {
			// path validated
			p.validated = true
			pm.logger.Debugf("path %s validated", p.addr)
			break
		}
	}
}

// SwitchToPath is called when the connection switches to a new path
func (pm *pathManager) SwitchToPath(addr net.Addr) {
	// retire all other paths
	for id := range pm.paths {
		if addrsEqual(pm.paths[id].addr, addr) {
			pm.logger.Debugf("switching to path %d (%s)", id, addr)
			continue
		}
		pm.retireConnID(id)
	}
	clear(pm.paths)
}

type pathManagerAckHandler pathManager

var _ ackhandler.FrameHandler = &pathManagerAckHandler{}

// Acknowledging the frame doesn't validate the path, only receiving the PATH_RESPONSE does.
func (pm *pathManagerAckHandler) OnAcked(f wire.Frame) {}

func (pm *pathManagerAckHandler) OnLost(f wire.Frame) {
	// TODO: retransmit the packet the first time it is lost
	pc := f.(*wire.PathChallengeFrame)
	for id, path := range pm.paths {
		if path.pathChallenge == pc.Data {
			delete(pm.paths, id)
			pm.retireConnID(id)
			break
		}
	}
}

func addrsEqual(addr1, addr2 net.Addr) bool {
	if addr1 == nil || addr2 == nil {
		return false
	}
	a1, ok1 := addr1.(*net.UDPAddr)
	a2, ok2 := addr2.(*net.UDPAddr)
	if ok1 && ok2 {
		return a1.IP.Equal(a2.IP) && a1.Port == a2.Port
	}
	return addr1.String() == addr2.String()
}