github.com/sagernet/gvisor@v0.0.0-20240428053021-e691de28565f/pkg/tcpip/network/internal/multicast/route_table.go (about) 1 // Copyright 2022 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package multicast contains utilities for supporting multicast routing. 16 package multicast 17 18 import ( 19 "errors" 20 "fmt" 21 "sync" 22 "time" 23 24 "github.com/sagernet/gvisor/pkg/tcpip" 25 "github.com/sagernet/gvisor/pkg/tcpip/stack" 26 ) 27 28 // RouteTable represents a multicast routing table. 29 type RouteTable struct { 30 // Internally, installed and pending routes are stored and locked separately 31 // A couple of reasons for structuring the table this way: 32 // 33 // 1. We can avoid write locking installed routes when pending packets are 34 // being queued. In other words, the happy path of reading installed 35 // routes doesn't require an exclusive lock. 36 // 2. The cleanup process for expired routes only needs to operate on pending 37 // routes. Like above, a write lock on the installed routes can be 38 // avoided. 39 // 3. This structure is similar to the Linux implementation: 40 // https://github.com/torvalds/linux/blob/cffb2b72d3e/include/linux/mroute_base.h#L250 41 42 // The installedMu lock should typically be acquired before the pendingMu 43 // lock. This ensures that installed routes can continue to be read even when 44 // the pending routes are write locked. 45 46 installedMu sync.RWMutex 47 // Maintaining pointers ensures that the installed routes are exclusively 48 // locked only when a route is being installed. 49 // +checklocks:installedMu 50 installedRoutes map[stack.UnicastSourceAndMulticastDestination]*InstalledRoute 51 52 pendingMu sync.RWMutex 53 // +checklocks:pendingMu 54 pendingRoutes map[stack.UnicastSourceAndMulticastDestination]PendingRoute 55 // cleanupPendingRoutesTimer is a timer that triggers a routine to remove 56 // pending routes that are expired. 57 // +checklocks:pendingMu 58 cleanupPendingRoutesTimer tcpip.Timer 59 // +checklocks:pendingMu 60 isCleanupRoutineRunning bool 61 62 config Config 63 } 64 65 var ( 66 // ErrNoBufferSpace indicates that no buffer space is available in the 67 // pending route packet queue. 68 ErrNoBufferSpace = errors.New("unable to queue packet, no buffer space available") 69 70 // ErrMissingClock indicates that a clock was not provided as part of the 71 // Config, but is required. 72 ErrMissingClock = errors.New("clock must not be nil") 73 74 // ErrAlreadyInitialized indicates that RouteTable.Init was already invoked. 75 ErrAlreadyInitialized = errors.New("table is already initialized") 76 ) 77 78 // InstalledRoute represents a route that is in the installed state. 79 // 80 // If a route is in the installed state, then it may be used to forward 81 // multicast packets. 82 type InstalledRoute struct { 83 stack.MulticastRoute 84 85 lastUsedTimestampMu sync.RWMutex 86 // +checklocks:lastUsedTimestampMu 87 lastUsedTimestamp tcpip.MonotonicTime 88 } 89 90 // LastUsedTimestamp returns a monotonic timestamp that corresponds to the last 91 // time the route was used or updated. 92 func (r *InstalledRoute) LastUsedTimestamp() tcpip.MonotonicTime { 93 r.lastUsedTimestampMu.RLock() 94 defer r.lastUsedTimestampMu.RUnlock() 95 96 return r.lastUsedTimestamp 97 } 98 99 // SetLastUsedTimestamp sets the time that the route was last used. 100 // 101 // The timestamp is only updated if it occurs after the currently set 102 // timestamp. Callers should invoke this anytime the route is used to forward a 103 // packet. 104 func (r *InstalledRoute) SetLastUsedTimestamp(monotonicTime tcpip.MonotonicTime) { 105 r.lastUsedTimestampMu.Lock() 106 defer r.lastUsedTimestampMu.Unlock() 107 108 if monotonicTime.After(r.lastUsedTimestamp) { 109 r.lastUsedTimestamp = monotonicTime 110 } 111 } 112 113 // PendingRoute represents a route that is in the "pending" state. 114 // 115 // A route is in the pending state if an installed route does not yet exist 116 // for the entry. For such routes, packets are added to an expiring queue until 117 // a route is installed. 118 type PendingRoute struct { 119 packets []*stack.PacketBuffer 120 121 // expiration is the timestamp at which the pending route should be expired. 122 // 123 // If this value is before the current time, then this pending route will 124 // be dropped. 125 expiration tcpip.MonotonicTime 126 } 127 128 func (p *PendingRoute) releasePackets() { 129 for _, pkt := range p.packets { 130 pkt.DecRef() 131 } 132 } 133 134 func (p *PendingRoute) isExpired(currentTime tcpip.MonotonicTime) bool { 135 return currentTime.After(p.expiration) 136 } 137 138 const ( 139 // DefaultMaxPendingQueueSize corresponds to the number of elements that can 140 // be in the packet queue for a pending route. 141 // 142 // Matches the Linux default queue size: 143 // https://github.com/torvalds/linux/blob/26291c54e11/net/ipv6/ip6mr.c#L1186 144 DefaultMaxPendingQueueSize uint8 = 3 145 146 // DefaultPendingRouteExpiration is the default maximum lifetime of a pending 147 // route. 148 // 149 // Matches the Linux default: 150 // https://github.com/torvalds/linux/blob/26291c54e11/net/ipv6/ip6mr.c#L991 151 DefaultPendingRouteExpiration time.Duration = 10 * time.Second 152 153 // DefaultCleanupInterval is the default frequency of the routine that 154 // expires pending routes. 155 // 156 // Matches the Linux default: 157 // https://github.com/torvalds/linux/blob/26291c54e11/net/ipv6/ip6mr.c#L793 158 DefaultCleanupInterval time.Duration = 10 * time.Second 159 ) 160 161 // Config represents the options for configuring a RouteTable. 162 type Config struct { 163 // MaxPendingQueueSize corresponds to the maximum number of queued packets 164 // for a pending route. 165 // 166 // If the caller attempts to queue a packet and the queue already contains 167 // MaxPendingQueueSize elements, then the packet will be rejected and should 168 // not be forwarded. 169 MaxPendingQueueSize uint8 170 171 // Clock represents the clock that should be used to obtain the current time. 172 // 173 // This field is required and must have a non-nil value. 174 Clock tcpip.Clock 175 } 176 177 // DefaultConfig returns the default configuration for the table. 178 func DefaultConfig(clock tcpip.Clock) Config { 179 return Config{ 180 MaxPendingQueueSize: DefaultMaxPendingQueueSize, 181 Clock: clock, 182 } 183 } 184 185 // Init initializes the RouteTable with the provided config. 186 // 187 // An error is returned if the config is not valid. 188 // 189 // Must be called before any other function on the table. 190 func (r *RouteTable) Init(config Config) error { 191 r.installedMu.Lock() 192 defer r.installedMu.Unlock() 193 r.pendingMu.Lock() 194 defer r.pendingMu.Unlock() 195 196 if r.installedRoutes != nil { 197 return ErrAlreadyInitialized 198 } 199 200 if config.Clock == nil { 201 return ErrMissingClock 202 } 203 204 r.config = config 205 r.installedRoutes = make(map[stack.UnicastSourceAndMulticastDestination]*InstalledRoute) 206 r.pendingRoutes = make(map[stack.UnicastSourceAndMulticastDestination]PendingRoute) 207 208 return nil 209 } 210 211 // Close cleans up resources held by the table. 212 // 213 // Calling this will stop the cleanup routine and release any packets owned by 214 // the table. 215 func (r *RouteTable) Close() { 216 r.pendingMu.Lock() 217 defer r.pendingMu.Unlock() 218 219 if r.cleanupPendingRoutesTimer != nil { 220 r.cleanupPendingRoutesTimer.Stop() 221 } 222 223 for key, route := range r.pendingRoutes { 224 delete(r.pendingRoutes, key) 225 route.releasePackets() 226 } 227 } 228 229 // maybeStopCleanupRoutine stops the pending routes cleanup routine if no 230 // pending routes exist. 231 // 232 // Returns true if the timer is not running. Otherwise, returns false. 233 // 234 // +checklocks:r.pendingMu 235 func (r *RouteTable) maybeStopCleanupRoutineLocked() bool { 236 if !r.isCleanupRoutineRunning { 237 return true 238 } 239 240 if len(r.pendingRoutes) == 0 { 241 r.cleanupPendingRoutesTimer.Stop() 242 r.isCleanupRoutineRunning = false 243 return true 244 } 245 246 return false 247 } 248 249 func (r *RouteTable) cleanupPendingRoutes() { 250 currentTime := r.config.Clock.NowMonotonic() 251 r.pendingMu.Lock() 252 defer r.pendingMu.Unlock() 253 254 for key, route := range r.pendingRoutes { 255 if route.isExpired(currentTime) { 256 delete(r.pendingRoutes, key) 257 route.releasePackets() 258 } 259 } 260 261 if stopped := r.maybeStopCleanupRoutineLocked(); !stopped { 262 r.cleanupPendingRoutesTimer.Reset(DefaultCleanupInterval) 263 } 264 } 265 266 func (r *RouteTable) newPendingRoute() PendingRoute { 267 return PendingRoute{ 268 packets: make([]*stack.PacketBuffer, 0, r.config.MaxPendingQueueSize), 269 expiration: r.config.Clock.NowMonotonic().Add(DefaultPendingRouteExpiration), 270 } 271 } 272 273 // NewInstalledRoute instantiates an installed route for the table. 274 func (r *RouteTable) NewInstalledRoute(route stack.MulticastRoute) *InstalledRoute { 275 return &InstalledRoute{ 276 MulticastRoute: route, 277 lastUsedTimestamp: r.config.Clock.NowMonotonic(), 278 } 279 } 280 281 // GetRouteResult represents the result of calling GetRouteOrInsertPending. 282 type GetRouteResult struct { 283 // GetRouteResultState signals the result of calling GetRouteOrInsertPending. 284 GetRouteResultState GetRouteResultState 285 286 // InstalledRoute represents the existing installed route. This field will 287 // only be populated if the GetRouteResultState is InstalledRouteFound. 288 InstalledRoute *InstalledRoute 289 } 290 291 // GetRouteResultState signals the result of calling GetRouteOrInsertPending. 292 type GetRouteResultState uint8 293 294 const ( 295 // InstalledRouteFound indicates that an InstalledRoute was found. 296 InstalledRouteFound GetRouteResultState = iota 297 298 // PacketQueuedInPendingRoute indicates that the packet was queued in an 299 // existing pending route. 300 PacketQueuedInPendingRoute 301 302 // NoRouteFoundAndPendingInserted indicates that no route was found and that 303 // a pending route was newly inserted into the RouteTable. 304 NoRouteFoundAndPendingInserted 305 ) 306 307 func (e GetRouteResultState) String() string { 308 switch e { 309 case InstalledRouteFound: 310 return "InstalledRouteFound" 311 case PacketQueuedInPendingRoute: 312 return "PacketQueuedInPendingRoute" 313 case NoRouteFoundAndPendingInserted: 314 return "NoRouteFoundAndPendingInserted" 315 default: 316 return fmt.Sprintf("%d", uint8(e)) 317 } 318 } 319 320 // GetRouteOrInsertPending attempts to fetch the installed route that matches 321 // the provided key. 322 // 323 // If no matching installed route is found, then the pkt is cloned and queued 324 // in a pending route. The GetRouteResult.GetRouteResultState will indicate 325 // whether the pkt was queued in a new pending route or an existing one. 326 // 327 // If the relevant pending route queue is at max capacity, then returns false. 328 // Otherwise, returns true. 329 func (r *RouteTable) GetRouteOrInsertPending(key stack.UnicastSourceAndMulticastDestination, pkt *stack.PacketBuffer) (GetRouteResult, bool) { 330 r.installedMu.RLock() 331 defer r.installedMu.RUnlock() 332 333 if route, ok := r.installedRoutes[key]; ok { 334 return GetRouteResult{GetRouteResultState: InstalledRouteFound, InstalledRoute: route}, true 335 } 336 337 r.pendingMu.Lock() 338 defer r.pendingMu.Unlock() 339 340 pendingRoute, getRouteResultState := r.getOrCreatePendingRouteRLocked(key) 341 if len(pendingRoute.packets) >= int(r.config.MaxPendingQueueSize) { 342 // The incoming packet is rejected if the pending queue is already at max 343 // capacity. This behavior matches the Linux implementation: 344 // https://github.com/torvalds/linux/blob/ae085d7f936/net/ipv4/ipmr.c#L1147 345 return GetRouteResult{}, false 346 } 347 pendingRoute.packets = append(pendingRoute.packets, pkt.Clone()) 348 r.pendingRoutes[key] = pendingRoute 349 350 if !r.isCleanupRoutineRunning { 351 // The cleanup routine isn't running, but should be. Start it. 352 if r.cleanupPendingRoutesTimer == nil { 353 r.cleanupPendingRoutesTimer = r.config.Clock.AfterFunc(DefaultCleanupInterval, r.cleanupPendingRoutes) 354 } else { 355 r.cleanupPendingRoutesTimer.Reset(DefaultCleanupInterval) 356 } 357 r.isCleanupRoutineRunning = true 358 } 359 360 return GetRouteResult{GetRouteResultState: getRouteResultState, InstalledRoute: nil}, true 361 } 362 363 // +checklocks:r.pendingMu 364 func (r *RouteTable) getOrCreatePendingRouteRLocked(key stack.UnicastSourceAndMulticastDestination) (PendingRoute, GetRouteResultState) { 365 if pendingRoute, ok := r.pendingRoutes[key]; ok { 366 return pendingRoute, PacketQueuedInPendingRoute 367 } 368 return r.newPendingRoute(), NoRouteFoundAndPendingInserted 369 } 370 371 // AddInstalledRoute adds the provided route to the table. 372 // 373 // Packets that were queued while the route was in the pending state are 374 // returned. The caller assumes ownership of these packets and is responsible 375 // for forwarding and releasing them. If an installed route already exists for 376 // the provided key, then it is overwritten. 377 func (r *RouteTable) AddInstalledRoute(key stack.UnicastSourceAndMulticastDestination, route *InstalledRoute) []*stack.PacketBuffer { 378 r.installedMu.Lock() 379 defer r.installedMu.Unlock() 380 r.installedRoutes[key] = route 381 382 r.pendingMu.Lock() 383 pendingRoute, ok := r.pendingRoutes[key] 384 delete(r.pendingRoutes, key) 385 // No need to reset the timer here. The cleanup routine is responsible for 386 // doing so. 387 _ = r.maybeStopCleanupRoutineLocked() 388 r.pendingMu.Unlock() 389 390 // Ignore the pending route if it is expired. It may be in this state since 391 // the cleanup process is only run periodically. 392 if !ok || pendingRoute.isExpired(r.config.Clock.NowMonotonic()) { 393 pendingRoute.releasePackets() 394 return nil 395 } 396 397 return pendingRoute.packets 398 } 399 400 // RemoveInstalledRoute deletes any installed route that matches the provided 401 // key. 402 // 403 // Returns true if a route was removed. Otherwise returns false. 404 func (r *RouteTable) RemoveInstalledRoute(key stack.UnicastSourceAndMulticastDestination) bool { 405 r.installedMu.Lock() 406 defer r.installedMu.Unlock() 407 408 if _, ok := r.installedRoutes[key]; ok { 409 delete(r.installedRoutes, key) 410 return true 411 } 412 413 return false 414 } 415 416 // RemoveAllInstalledRoutes removes all installed routes from the table. 417 func (r *RouteTable) RemoveAllInstalledRoutes() { 418 r.installedMu.Lock() 419 defer r.installedMu.Unlock() 420 421 for key := range r.installedRoutes { 422 delete(r.installedRoutes, key) 423 } 424 } 425 426 // GetLastUsedTimestamp returns a monotonic timestamp that represents the last 427 // time the route that matches the provided key was used or updated. 428 // 429 // Returns true if a matching route was found. Otherwise returns false. 430 func (r *RouteTable) GetLastUsedTimestamp(key stack.UnicastSourceAndMulticastDestination) (tcpip.MonotonicTime, bool) { 431 r.installedMu.RLock() 432 defer r.installedMu.RUnlock() 433 434 if route, ok := r.installedRoutes[key]; ok { 435 return route.LastUsedTimestamp(), true 436 } 437 return tcpip.MonotonicTime{}, false 438 }