github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/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/metacubex/gvisor/pkg/tcpip"
    25  	"github.com/metacubex/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  }