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
}
|