File: route_table.go

package info (click to toggle)
golang-gvisor-gvisor 0.0~20221219.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 17,136 kB
  • sloc: asm: 2,860; cpp: 348; python: 89; sh: 40; makefile: 34; ansic: 21
file content (438 lines) | stat: -rw-r--r-- 14,405 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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
// Copyright 2022 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package multicast contains utilities for supporting multicast routing.
package multicast

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"gvisor.dev/gvisor/pkg/tcpip"
	"gvisor.dev/gvisor/pkg/tcpip/stack"
)

// RouteTable represents a multicast routing table.
type RouteTable struct {
	// Internally, installed and pending routes are stored and locked separately
	// A couple of reasons for structuring the table this way:
	//
	// 1. We can avoid write locking installed routes when pending packets are
	//		being queued. In other words, the happy path of reading installed
	//		routes doesn't require an exclusive lock.
	// 2. The cleanup process for expired routes only needs to operate on pending
	//		routes. Like above, a write lock on the installed routes can be
	//		avoided.
	// 3. This structure is similar to the Linux implementation:
	//		https://github.com/torvalds/linux/blob/cffb2b72d3e/include/linux/mroute_base.h#L250

	// The installedMu lock should typically be acquired before the pendingMu
	// lock. This ensures that installed routes can continue to be read even when
	// the pending routes are write locked.

	installedMu sync.RWMutex
	// Maintaining pointers ensures that the installed routes are exclusively
	// locked only when a route is being installed.
	// +checklocks:installedMu
	installedRoutes map[stack.UnicastSourceAndMulticastDestination]*InstalledRoute

	pendingMu sync.RWMutex
	// +checklocks:pendingMu
	pendingRoutes map[stack.UnicastSourceAndMulticastDestination]PendingRoute
	// cleanupPendingRoutesTimer is a timer that triggers a routine to remove
	// pending routes that are expired.
	// +checklocks:pendingMu
	cleanupPendingRoutesTimer tcpip.Timer
	// +checklocks:pendingMu
	isCleanupRoutineRunning bool

	config Config
}

var (
	// ErrNoBufferSpace indicates that no buffer space is available in the
	// pending route packet queue.
	ErrNoBufferSpace = errors.New("unable to queue packet, no buffer space available")

	// ErrMissingClock indicates that a clock was not provided as part of the
	// Config, but is required.
	ErrMissingClock = errors.New("clock must not be nil")

	// ErrAlreadyInitialized indicates that RouteTable.Init was already invoked.
	ErrAlreadyInitialized = errors.New("table is already initialized")
)

// InstalledRoute represents a route that is in the installed state.
//
// If a route is in the installed state, then it may be used to forward
// multicast packets.
type InstalledRoute struct {
	stack.MulticastRoute

	lastUsedTimestampMu sync.RWMutex
	// +checklocks:lastUsedTimestampMu
	lastUsedTimestamp tcpip.MonotonicTime
}

// LastUsedTimestamp returns a monotonic timestamp that corresponds to the last
// time the route was used or updated.
func (r *InstalledRoute) LastUsedTimestamp() tcpip.MonotonicTime {
	r.lastUsedTimestampMu.RLock()
	defer r.lastUsedTimestampMu.RUnlock()

	return r.lastUsedTimestamp
}

// SetLastUsedTimestamp sets the time that the route was last used.
//
// The timestamp is only updated if it occurs after the currently set
// timestamp. Callers should invoke this anytime the route is used to forward a
// packet.
func (r *InstalledRoute) SetLastUsedTimestamp(monotonicTime tcpip.MonotonicTime) {
	r.lastUsedTimestampMu.Lock()
	defer r.lastUsedTimestampMu.Unlock()

	if monotonicTime.After(r.lastUsedTimestamp) {
		r.lastUsedTimestamp = monotonicTime
	}
}

// PendingRoute represents a route that is in the "pending" state.
//
// A route is in the pending state if an installed route does not yet exist
// for the entry. For such routes, packets are added to an expiring queue until
// a route is installed.
type PendingRoute struct {
	packets []stack.PacketBufferPtr

	// expiration is the timestamp at which the pending route should be expired.
	//
	// If this value is before the current time, then this pending route will
	// be dropped.
	expiration tcpip.MonotonicTime
}

func (p *PendingRoute) releasePackets() {
	for _, pkt := range p.packets {
		pkt.DecRef()
	}
}

func (p *PendingRoute) isExpired(currentTime tcpip.MonotonicTime) bool {
	return currentTime.After(p.expiration)
}

const (
	// DefaultMaxPendingQueueSize corresponds to the number of elements that can
	// be in the packet queue for a pending route.
	//
	// Matches the Linux default queue size:
	// https://github.com/torvalds/linux/blob/26291c54e11/net/ipv6/ip6mr.c#L1186
	DefaultMaxPendingQueueSize uint8 = 3

	// DefaultPendingRouteExpiration is the default maximum lifetime of a pending
	// route.
	//
	// Matches the Linux default:
	// https://github.com/torvalds/linux/blob/26291c54e11/net/ipv6/ip6mr.c#L991
	DefaultPendingRouteExpiration time.Duration = 10 * time.Second

	// DefaultCleanupInterval is the default frequency of the routine that
	// expires pending routes.
	//
	// Matches the Linux default:
	// https://github.com/torvalds/linux/blob/26291c54e11/net/ipv6/ip6mr.c#L793
	DefaultCleanupInterval time.Duration = 10 * time.Second
)

// Config represents the options for configuring a RouteTable.
type Config struct {
	// MaxPendingQueueSize corresponds to the maximum number of queued packets
	// for a pending route.
	//
	// If the caller attempts to queue a packet and the queue already contains
	// MaxPendingQueueSize elements, then the packet will be rejected and should
	// not be forwarded.
	MaxPendingQueueSize uint8

	// Clock represents the clock that should be used to obtain the current time.
	//
	// This field is required and must have a non-nil value.
	Clock tcpip.Clock
}

// DefaultConfig returns the default configuration for the table.
func DefaultConfig(clock tcpip.Clock) Config {
	return Config{
		MaxPendingQueueSize: DefaultMaxPendingQueueSize,
		Clock:               clock,
	}
}

// Init initializes the RouteTable with the provided config.
//
// An error is returned if the config is not valid.
//
// Must be called before any other function on the table.
func (r *RouteTable) Init(config Config) error {
	r.installedMu.Lock()
	defer r.installedMu.Unlock()
	r.pendingMu.Lock()
	defer r.pendingMu.Unlock()

	if r.installedRoutes != nil {
		return ErrAlreadyInitialized
	}

	if config.Clock == nil {
		return ErrMissingClock
	}

	r.config = config
	r.installedRoutes = make(map[stack.UnicastSourceAndMulticastDestination]*InstalledRoute)
	r.pendingRoutes = make(map[stack.UnicastSourceAndMulticastDestination]PendingRoute)

	return nil
}

// Close cleans up resources held by the table.
//
// Calling this will stop the cleanup routine and release any packets owned by
// the table.
func (r *RouteTable) Close() {
	r.pendingMu.Lock()
	defer r.pendingMu.Unlock()

	if r.cleanupPendingRoutesTimer != nil {
		r.cleanupPendingRoutesTimer.Stop()
	}

	for key, route := range r.pendingRoutes {
		delete(r.pendingRoutes, key)
		route.releasePackets()
	}
}

// maybeStopCleanupRoutine stops the pending routes cleanup routine if no
// pending routes exist.
//
// Returns true if the timer is not running. Otherwise, returns false.
//
// +checklocks:r.pendingMu
func (r *RouteTable) maybeStopCleanupRoutineLocked() bool {
	if !r.isCleanupRoutineRunning {
		return true
	}

	if len(r.pendingRoutes) == 0 {
		r.cleanupPendingRoutesTimer.Stop()
		r.isCleanupRoutineRunning = false
		return true
	}

	return false
}

func (r *RouteTable) cleanupPendingRoutes() {
	currentTime := r.config.Clock.NowMonotonic()
	r.pendingMu.Lock()
	defer r.pendingMu.Unlock()

	for key, route := range r.pendingRoutes {
		if route.isExpired(currentTime) {
			delete(r.pendingRoutes, key)
			route.releasePackets()
		}
	}

	if stopped := r.maybeStopCleanupRoutineLocked(); !stopped {
		r.cleanupPendingRoutesTimer.Reset(DefaultCleanupInterval)
	}
}

func (r *RouteTable) newPendingRoute() PendingRoute {
	return PendingRoute{
		packets:    make([]stack.PacketBufferPtr, 0, r.config.MaxPendingQueueSize),
		expiration: r.config.Clock.NowMonotonic().Add(DefaultPendingRouteExpiration),
	}
}

// NewInstalledRoute instantiates an installed route for the table.
func (r *RouteTable) NewInstalledRoute(route stack.MulticastRoute) *InstalledRoute {
	return &InstalledRoute{
		MulticastRoute:    route,
		lastUsedTimestamp: r.config.Clock.NowMonotonic(),
	}
}

// GetRouteResult represents the result of calling GetRouteOrInsertPending.
type GetRouteResult struct {
	// GetRouteResultState signals the result of calling GetRouteOrInsertPending.
	GetRouteResultState GetRouteResultState

	// InstalledRoute represents the existing installed route. This field will
	// only be populated if the GetRouteResultState is InstalledRouteFound.
	InstalledRoute *InstalledRoute
}

// GetRouteResultState signals the result of calling GetRouteOrInsertPending.
type GetRouteResultState uint8

const (
	// InstalledRouteFound indicates that an InstalledRoute was found.
	InstalledRouteFound GetRouteResultState = iota

	// PacketQueuedInPendingRoute indicates that the packet was queued in an
	// existing pending route.
	PacketQueuedInPendingRoute

	// NoRouteFoundAndPendingInserted indicates that no route was found and that
	// a pending route was newly inserted into the RouteTable.
	NoRouteFoundAndPendingInserted
)

func (e GetRouteResultState) String() string {
	switch e {
	case InstalledRouteFound:
		return "InstalledRouteFound"
	case PacketQueuedInPendingRoute:
		return "PacketQueuedInPendingRoute"
	case NoRouteFoundAndPendingInserted:
		return "NoRouteFoundAndPendingInserted"
	default:
		return fmt.Sprintf("%d", uint8(e))
	}
}

// GetRouteOrInsertPending attempts to fetch the installed route that matches
// the provided key.
//
// If no matching installed route is found, then the pkt is cloned and queued
// in a pending route. The GetRouteResult.GetRouteResultState will indicate
// whether the pkt was queued in a new pending route or an existing one.
//
// If the relevant pending route queue is at max capacity, then returns false.
// Otherwise, returns true.
func (r *RouteTable) GetRouteOrInsertPending(key stack.UnicastSourceAndMulticastDestination, pkt stack.PacketBufferPtr) (GetRouteResult, bool) {
	r.installedMu.RLock()
	defer r.installedMu.RUnlock()

	if route, ok := r.installedRoutes[key]; ok {
		return GetRouteResult{GetRouteResultState: InstalledRouteFound, InstalledRoute: route}, true
	}

	r.pendingMu.Lock()
	defer r.pendingMu.Unlock()

	pendingRoute, getRouteResultState := r.getOrCreatePendingRouteRLocked(key)
	if len(pendingRoute.packets) >= int(r.config.MaxPendingQueueSize) {
		// The incoming packet is rejected if the pending queue is already at max
		// capacity. This behavior matches the Linux implementation:
		// https://github.com/torvalds/linux/blob/ae085d7f936/net/ipv4/ipmr.c#L1147
		return GetRouteResult{}, false
	}
	pendingRoute.packets = append(pendingRoute.packets, pkt.Clone())
	r.pendingRoutes[key] = pendingRoute

	if !r.isCleanupRoutineRunning {
		// The cleanup routine isn't running, but should be. Start it.
		if r.cleanupPendingRoutesTimer == nil {
			r.cleanupPendingRoutesTimer = r.config.Clock.AfterFunc(DefaultCleanupInterval, r.cleanupPendingRoutes)
		} else {
			r.cleanupPendingRoutesTimer.Reset(DefaultCleanupInterval)
		}
		r.isCleanupRoutineRunning = true
	}

	return GetRouteResult{GetRouteResultState: getRouteResultState, InstalledRoute: nil}, true
}

// +checklocks:r.pendingMu
func (r *RouteTable) getOrCreatePendingRouteRLocked(key stack.UnicastSourceAndMulticastDestination) (PendingRoute, GetRouteResultState) {
	if pendingRoute, ok := r.pendingRoutes[key]; ok {
		return pendingRoute, PacketQueuedInPendingRoute
	}
	return r.newPendingRoute(), NoRouteFoundAndPendingInserted
}

// AddInstalledRoute adds the provided route to the table.
//
// Packets that were queued while the route was in the pending state are
// returned. The caller assumes ownership of these packets and is responsible
// for forwarding and releasing them. If an installed route already exists for
// the provided key, then it is overwritten.
func (r *RouteTable) AddInstalledRoute(key stack.UnicastSourceAndMulticastDestination, route *InstalledRoute) []stack.PacketBufferPtr {
	r.installedMu.Lock()
	defer r.installedMu.Unlock()
	r.installedRoutes[key] = route

	r.pendingMu.Lock()
	pendingRoute, ok := r.pendingRoutes[key]
	delete(r.pendingRoutes, key)
	// No need to reset the timer here. The cleanup routine is responsible for
	// doing so.
	_ = r.maybeStopCleanupRoutineLocked()
	r.pendingMu.Unlock()

	// Ignore the pending route if it is expired. It may be in this state since
	// the cleanup process is only run periodically.
	if !ok || pendingRoute.isExpired(r.config.Clock.NowMonotonic()) {
		pendingRoute.releasePackets()
		return nil
	}

	return pendingRoute.packets
}

// RemoveInstalledRoute deletes any installed route that matches the provided
// key.
//
// Returns true if a route was removed. Otherwise returns false.
func (r *RouteTable) RemoveInstalledRoute(key stack.UnicastSourceAndMulticastDestination) bool {
	r.installedMu.Lock()
	defer r.installedMu.Unlock()

	if _, ok := r.installedRoutes[key]; ok {
		delete(r.installedRoutes, key)
		return true
	}

	return false
}

// RemoveAllInstalledRoutes removes all installed routes from the table.
func (r *RouteTable) RemoveAllInstalledRoutes() {
	r.installedMu.Lock()
	defer r.installedMu.Unlock()

	for key := range r.installedRoutes {
		delete(r.installedRoutes, key)
	}
}

// GetLastUsedTimestamp returns a monotonic timestamp that represents the last
// time the route that matches the provided key was used or updated.
//
// Returns true if a matching route was found. Otherwise returns false.
func (r *RouteTable) GetLastUsedTimestamp(key stack.UnicastSourceAndMulticastDestination) (tcpip.MonotonicTime, bool) {
	r.installedMu.RLock()
	defer r.installedMu.RUnlock()

	if route, ok := r.installedRoutes[key]; ok {
		return route.LastUsedTimestamp(), true
	}
	return tcpip.MonotonicTime{}, false
}