Cleanup the multicast routing table.

This change does the following:

- Uses shared types for the route key and multicast route.
- Returns a bool instead of error from GetRouteOrInsertPending since only one
error type is possible.
- Improves the naming of PendingRouteState using the suggestions in
cl/445157188.

Updates #7338.

PiperOrigin-RevId: 449812803
This commit is contained in:
Nate Hurley
2022-05-19 12:54:32 -07:00
committed by gVisor bot
parent ec422a6609
commit 28eda96b00
4 changed files with 133 additions and 126 deletions
@@ -32,6 +32,7 @@ import (
const (
defaultMinTTL = 10
defaultMTU = 1500
inputNICID tcpip.NICID = 1
outgoingNICID tcpip.NICID = 2
)
@@ -39,8 +40,9 @@ const (
// Example shows how to interact with a multicast RouteTable.
func Example() {
address := testutil.MustParse4("192.168.1.1")
defaultOutgoingInterfaces := []multicast.OutgoingInterface{{ID: outgoingNICID, MinTTL: defaultMinTTL}}
routeKey := multicast.RouteKey{UnicastSource: address, MulticastDestination: address}
defaultOutgoingInterfaces := []stack.MulticastRouteOutgoingInterface{{ID: outgoingNICID, MinTTL: defaultMinTTL}}
routeKey := stack.UnicastSourceAndMulticastDestination{Source: address, Destination: address}
multicastRoute := stack.MulticastRoute{inputNICID, defaultOutgoingInterfaces}
pkt := newPacketBuffer("hello")
defer pkt.DecRef()
@@ -59,34 +61,30 @@ func Example() {
// Each entry in the table represents either an installed route or a pending
// route. To insert a pending route, call:
result, err := table.GetRouteOrInsertPending(routeKey, pkt)
result, hasBufferSpace := table.GetRouteOrInsertPending(routeKey, pkt)
// Callers should handle a no buffer space error (e.g. only deliver the
// packet locally).
if err == multicast.ErrNoBufferSpace {
if !hasBufferSpace {
deliverPktLocally(pkt)
}
if err != nil {
panic(err)
}
// Callers should handle the various pending route states.
switch result.PendingRouteState {
case multicast.PendingRouteStateNone:
switch result.GetRouteResultState {
case multicast.InstalledRouteFound:
// The packet can be forwarded using the installed route.
forwardPkt(pkt, result.InstalledRoute)
case multicast.PendingRouteStateInstalled:
case multicast.NoRouteFoundAndPendingInserted:
// The route has just entered the pending state.
emitMissingRouteEvent(routeKey)
deliverPktLocally(pkt)
case multicast.PendingRouteStateAppended:
case multicast.PacketQueuedInPendingRoute:
// The route was already in the pending state.
deliverPktLocally(pkt)
}
// To transition a pending route to the installed state, call:
route := table.NewInstalledRoute(inputNICID, defaultOutgoingInterfaces)
route := table.NewInstalledRoute(multicastRoute)
pendingPackets := table.AddInstalledRoute(routeKey, route)
// If there was a pending route, then the caller is responsible for
@@ -121,7 +119,7 @@ func forwardPkt(*stack.PacketBuffer, *multicast.InstalledRoute) {
fmt.Println("forwardPkt")
}
func emitMissingRouteEvent(multicast.RouteKey) {
func emitMissingRouteEvent(stack.UnicastSourceAndMulticastDestination) {
fmt.Println("emitMissingRouteEvent")
}
@@ -47,11 +47,11 @@ type RouteTable struct {
// Maintaining pointers ensures that the installed routes are exclusively
// locked only when a route is being installed.
// +checklocks:installedMu
installedRoutes map[RouteKey]*InstalledRoute
installedRoutes map[stack.UnicastSourceAndMulticastDestination]*InstalledRoute
pendingMu sync.RWMutex
// +checklocks:pendingMu
pendingRoutes map[RouteKey]PendingRoute
pendingRoutes map[stack.UnicastSourceAndMulticastDestination]PendingRoute
// cleanupPendingRoutesTimer is a timer that triggers a routine to remove
// pending routes that are expired.
// +checklocks:pendingMu
@@ -75,35 +75,18 @@ var (
ErrAlreadyInitialized = errors.New("table is already initialized")
)
// RouteKey represents an entry key in the RouteTable.
type RouteKey struct {
UnicastSource tcpip.Address
MulticastDestination tcpip.Address
}
// 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 {
expectedInputInterface tcpip.NICID
outgoingInterfaces []OutgoingInterface
stack.MulticastRoute
lastUsedTimestampMu sync.RWMutex
// +checklocks:lastUsedTimestampMu
lastUsedTimestamp tcpip.MonotonicTime
}
// ExpectedInputInterface returns the expected input interface for the route.
func (r *InstalledRoute) ExpectedInputInterface() tcpip.NICID {
return r.expectedInputInterface
}
// OutgoingInterfaces returns the outgoing interfaces for the route.
func (r *InstalledRoute) OutgoingInterfaces() []OutgoingInterface {
return r.outgoingInterfaces
}
// LastUsedTimestamp returns a monotonic timestamp that corresponds to the last
// time the route was used or updated.
func (r *InstalledRoute) LastUsedTimestamp() tcpip.MonotonicTime {
@@ -127,17 +110,6 @@ func (r *InstalledRoute) SetLastUsedTimestamp(monotonicTime tcpip.MonotonicTime)
}
}
// OutgoingInterface represents an interface that packets should be forwarded
// out of.
type OutgoingInterface struct {
// ID corresponds to the outgoing NIC.
ID tcpip.NICID
// MinTTL represents the minumum TTL/HopLimit a multicast packet must have to
// be sent through the outgoing interface.
MinTTL uint8
}
// 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
@@ -230,8 +202,8 @@ func (r *RouteTable) Init(config Config) error {
}
r.config = config
r.installedRoutes = make(map[RouteKey]*InstalledRoute)
r.pendingRoutes = make(map[RouteKey]PendingRoute)
r.installedRoutes = make(map[stack.UnicastSourceAndMulticastDestination]*InstalledRoute)
r.pendingRoutes = make(map[stack.UnicastSourceAndMulticastDestination]PendingRoute)
return nil
}
@@ -299,53 +271,47 @@ func (r *RouteTable) newPendingRoute() PendingRoute {
}
// NewInstalledRoute instantiates an installed route for the table.
func (r *RouteTable) NewInstalledRoute(inputInterface tcpip.NICID, outgoingInterfaces []OutgoingInterface) *InstalledRoute {
func (r *RouteTable) NewInstalledRoute(route stack.MulticastRoute) *InstalledRoute {
return &InstalledRoute{
expectedInputInterface: inputInterface,
outgoingInterfaces: outgoingInterfaces,
lastUsedTimestamp: r.config.Clock.NowMonotonic(),
MulticastRoute: route,
lastUsedTimestamp: r.config.Clock.NowMonotonic(),
}
}
// GetRouteResult represents the result of calling
// RouteTable.GetRouteOrInsertPending.
// GetRouteResult represents the result of calling GetRouteOrInsertPending.
type GetRouteResult struct {
// PendingRouteState represents the observed state of any applicable
// PendingRoute.
PendingRouteState
// GetRouteResultState signals the result of calling GetRouteOrInsertPending.
GetRouteResultState GetRouteResultState
// InstalledRoute represents the existing installed route. This field will
// only be populated if the PendingRouteState is PendingRouteStateNone.
*InstalledRoute
// only be populated if the GetRouteResultState is InstalledRouteFound.
InstalledRoute *InstalledRoute
}
// PendingRouteState represents the state of a PendingRoute as observed by the
// RouteTable.GetRouteOrInsertPending method.
type PendingRouteState uint8
// GetRouteResultState signals the result of calling GetRouteOrInsertPending.
type GetRouteResultState uint8
const (
// PendingRouteStateNone indicates that no pending route exists. In such a
// case, the GetRouteResult will contain an InstalledRoute.
PendingRouteStateNone PendingRouteState = iota
// InstalledRouteFound indicates that an InstalledRoute was found.
InstalledRouteFound GetRouteResultState = iota
// PendingRouteStateAppended indicates that the packet was queued in an
// PacketQueuedInPendingRoute indicates that the packet was queued in an
// existing pending route.
PendingRouteStateAppended
PacketQueuedInPendingRoute
// PendingRouteStateInstalled indicates that a pending route was newly
// inserted into the RouteTable. In such a case, callers should typically
// emit a missing route event.
PendingRouteStateInstalled
// NoRouteFoundAndPendingInserted indicates that no route was found and that
// a pending route was newly inserted into the RouteTable.
NoRouteFoundAndPendingInserted
)
func (e PendingRouteState) String() string {
func (e GetRouteResultState) String() string {
switch e {
case PendingRouteStateNone:
return "PendingRouteStateNone"
case PendingRouteStateAppended:
return "PendingRouteStateAppended"
case PendingRouteStateInstalled:
return "PendingRouteStateInstalled"
case InstalledRouteFound:
return "InstalledRouteFound"
case PacketQueuedInPendingRoute:
return "PacketQueuedInPendingRoute"
case NoRouteFoundAndPendingInserted:
return "NoRouteFoundAndPendingInserted"
default:
return fmt.Sprintf("%d", uint8(e))
}
@@ -355,29 +321,28 @@ func (e PendingRouteState) String() string {
// the provided key.
//
// If no matching installed route is found, then the pkt is cloned and queued
// in a pending route. The GetRouteResult.PendingRouteState will indicate
// 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
// ErrNoBufferSpace is returned. In such a case, callers are typically expected
// to only deliver the pkt locally (if relevant).
func (r *RouteTable) GetRouteOrInsertPending(key RouteKey, pkt *stack.PacketBuffer) (GetRouteResult, error) {
// 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.PacketBuffer) (GetRouteResult, bool) {
r.installedMu.RLock()
defer r.installedMu.RUnlock()
if route, ok := r.installedRoutes[key]; ok {
return GetRouteResult{PendingRouteState: PendingRouteStateNone, InstalledRoute: route}, nil
return GetRouteResult{GetRouteResultState: InstalledRouteFound, InstalledRoute: route}, true
}
r.pendingMu.Lock()
defer r.pendingMu.Unlock()
pendingRoute, pendingRouteState := r.getOrCreatePendingRouteRLocked(key)
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{}, ErrNoBufferSpace
return GetRouteResult{}, false
}
pendingRoute.packets = append(pendingRoute.packets, pkt.Clone())
r.pendingRoutes[key] = pendingRoute
@@ -392,15 +357,15 @@ func (r *RouteTable) GetRouteOrInsertPending(key RouteKey, pkt *stack.PacketBuff
r.isCleanupRoutineRunning = true
}
return GetRouteResult{PendingRouteState: pendingRouteState, InstalledRoute: nil}, nil
return GetRouteResult{GetRouteResultState: getRouteResultState, InstalledRoute: nil}, true
}
// +checklocks:r.pendingMu
func (r *RouteTable) getOrCreatePendingRouteRLocked(key RouteKey) (PendingRoute, PendingRouteState) {
func (r *RouteTable) getOrCreatePendingRouteRLocked(key stack.UnicastSourceAndMulticastDestination) (PendingRoute, GetRouteResultState) {
if pendingRoute, ok := r.pendingRoutes[key]; ok {
return pendingRoute, PendingRouteStateAppended
return pendingRoute, PacketQueuedInPendingRoute
}
return r.newPendingRoute(), PendingRouteStateInstalled
return r.newPendingRoute(), NoRouteFoundAndPendingInserted
}
// AddInstalledRoute adds the provided route to the table.
@@ -409,7 +374,7 @@ func (r *RouteTable) getOrCreatePendingRouteRLocked(key RouteKey) (PendingRoute,
// 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 RouteKey, route *InstalledRoute) []*stack.PacketBuffer {
func (r *RouteTable) AddInstalledRoute(key stack.UnicastSourceAndMulticastDestination, route *InstalledRoute) []*stack.PacketBuffer {
r.installedMu.Lock()
defer r.installedMu.Unlock()
r.installedRoutes[key] = route
@@ -436,7 +401,7 @@ func (r *RouteTable) AddInstalledRoute(key RouteKey, route *InstalledRoute) []*s
// key.
//
// Returns true if a route was removed. Otherwise returns false.
func (r *RouteTable) RemoveInstalledRoute(key RouteKey) bool {
func (r *RouteTable) RemoveInstalledRoute(key stack.UnicastSourceAndMulticastDestination) bool {
r.installedMu.Lock()
defer r.installedMu.Unlock()
@@ -452,7 +417,7 @@ func (r *RouteTable) RemoveInstalledRoute(key RouteKey) bool {
// 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 RouteKey) (tcpip.MonotonicTime, bool) {
func (r *RouteTable) GetLastUsedTimestamp(key stack.UnicastSourceAndMulticastDestination) (tcpip.MonotonicTime, bool) {
r.installedMu.RLock()
defer r.installedMu.RUnlock()
@@ -32,6 +32,7 @@ import (
const (
defaultMinTTL = 10
defaultMTU = 1500
inputNICID tcpip.NICID = 1
outgoingNICID tcpip.NICID = 2
defaultNICID tcpip.NICID = 3
@@ -39,8 +40,9 @@ const (
var (
defaultAddress = testutil.MustParse4("192.168.1.1")
defaultRouteKey = RouteKey{UnicastSource: defaultAddress, MulticastDestination: defaultAddress}
defaultOutgoingInterfaces = []OutgoingInterface{{ID: outgoingNICID, MinTTL: defaultMinTTL}}
defaultRouteKey = stack.UnicastSourceAndMulticastDestination{Source: defaultAddress, Destination: defaultAddress}
defaultOutgoingInterfaces = []stack.MulticastRouteOutgoingInterface{{ID: outgoingNICID, MinTTL: defaultMinTTL}}
defaultRoute = stack.MulticastRoute{inputNICID, defaultOutgoingInterfaces}
)
func newPacketBuffer(body string) *stack.PacketBuffer {
@@ -77,11 +79,11 @@ func defaultConfig(opts ...configOption) Config {
}
func installedRouteComparer(a *InstalledRoute, b *InstalledRoute) bool {
if !cmp.Equal(a.OutgoingInterfaces(), b.OutgoingInterfaces()) {
if !cmp.Equal(a.OutgoingInterfaces, b.OutgoingInterfaces) {
return false
}
if a.ExpectedInputInterface() != b.ExpectedInputInterface() {
if a.ExpectedInputInterface != b.ExpectedInputInterface {
return false
}
@@ -143,15 +145,19 @@ func TestNewInstalledRoute(t *testing.T) {
t.Fatalf("table.Init(%#v): %s", config, err)
}
route := table.NewInstalledRoute(inputNICID, defaultOutgoingInterfaces)
expectedRoute := &InstalledRoute{expectedInputInterface: inputNICID, outgoingInterfaces: defaultOutgoingInterfaces, lastUsedTimestamp: clock.NowMonotonic()}
route := table.NewInstalledRoute(defaultRoute)
expectedRoute := &InstalledRoute{
MulticastRoute: defaultRoute,
lastUsedTimestamp: clock.NowMonotonic(),
}
if diff := cmp.Diff(expectedRoute, route, cmp.Comparer(installedRouteComparer)); diff != "" {
t.Errorf("Installed route mismatch (-want +got):\n%s", diff)
}
}
func TestPendingRouteStates(t *testing.T) {
func TestGetRouteResultStates(t *testing.T) {
table := RouteTable{}
defer table.Close()
config := defaultConfig(withMaxPendingQueueSize(2))
@@ -161,16 +167,17 @@ func TestPendingRouteStates(t *testing.T) {
pkt := newPacketBuffer("hello")
defer pkt.DecRef()
// Queue two pending packets for the same route. The PendingRouteState should
// transition from PendingRouteStateInstalled to PendingRouteStateAppended.
for _, wantPendingRouteState := range []PendingRouteState{PendingRouteStateInstalled, PendingRouteStateAppended} {
routeResult, err := table.GetRouteOrInsertPending(defaultRouteKey, pkt)
// Queue two pending packets for the same route. The GetRouteResultState
// should transition from NoRouteFoundAndPendingInserted to
// PacketQueuedInPendingRoute.
for _, wantPendingRouteState := range []GetRouteResultState{NoRouteFoundAndPendingInserted, PacketQueuedInPendingRoute} {
routeResult, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt)
if err != nil {
t.Errorf("table.GetRouteOrInsertPending(%#v, %#v) = (_, %v), want = (_, nil)", defaultRouteKey, pkt, err)
if !hasBufferSpace {
t.Errorf("table.GetRouteOrInsertPending(%#v, %#v) = (_, false), want = (_, true)", defaultRouteKey, pkt)
}
expectedResult := GetRouteResult{PendingRouteState: wantPendingRouteState}
expectedResult := GetRouteResult{GetRouteResultState: wantPendingRouteState}
if diff := cmp.Diff(expectedResult, routeResult); diff != "" {
t.Errorf("table.GetRouteOrInsertPending(%#v, %#v) GetRouteResult mismatch (-want +got):\n%s", defaultRouteKey, pkt, diff)
}
@@ -178,8 +185,8 @@ func TestPendingRouteStates(t *testing.T) {
// Queuing a third packet should yield an error since the pending queue is
// already at max capacity.
if _, err := table.GetRouteOrInsertPending(defaultRouteKey, pkt); err != ErrNoBufferSpace {
t.Errorf("table.GetRouteOrInsertPending(%#v, %#v) = (_, %v), want = (_, ErrNoBufferSpace)", defaultRouteKey, pkt, err)
if _, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt); hasBufferSpace {
t.Errorf("table.GetRouteOrInsertPending(%#v, %#v) = (_, true), want = (_, false)", defaultRouteKey, pkt)
}
}
@@ -225,8 +232,8 @@ func TestPendingRouteExpiration(t *testing.T) {
clock.Advance(test.advanceBeforeInsert)
if _, err := table.GetRouteOrInsertPending(defaultRouteKey, pkt); err != nil {
t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): %v", defaultRouteKey, pkt, err)
if _, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt); !hasBufferSpace {
t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): false", defaultRouteKey, pkt)
}
clock.Advance(test.advanceAfterInsert)
@@ -288,8 +295,8 @@ func TestAddInstalledRouteWithPending(t *testing.T) {
t.Fatalf("table.Init(%#v): %s", config, err)
}
if _, err := table.GetRouteOrInsertPending(defaultRouteKey, pkt); err != nil {
t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): %v", defaultRouteKey, pkt, err)
if _, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt); !hasBufferSpace {
t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): false", defaultRouteKey, pkt)
}
// Disable the cleanup routine.
@@ -297,7 +304,7 @@ func TestAddInstalledRouteWithPending(t *testing.T) {
clock.Advance(test.advance)
route := table.NewInstalledRoute(inputNICID, defaultOutgoingInterfaces)
route := table.NewInstalledRoute(defaultRoute)
pendingPackets := table.AddInstalledRoute(defaultRouteKey, route)
if diff := cmp.Diff(test.want, pendingPackets, cmpOpts...); diff != "" {
@@ -326,27 +333,29 @@ func TestAddInstalledRouteWithNoPending(t *testing.T) {
t.Fatalf("table.Init(%#v): %s", config, err)
}
firstRoute := table.NewInstalledRoute(inputNICID, defaultOutgoingInterfaces)
secondRoute := table.NewInstalledRoute(defaultNICID, defaultOutgoingInterfaces)
firstRoute := table.NewInstalledRoute(defaultRoute)
secondMulticastRoute := stack.MulticastRoute{defaultNICID, defaultOutgoingInterfaces}
secondRoute := table.NewInstalledRoute(secondMulticastRoute)
pkt := newPacketBuffer("hello")
defer pkt.DecRef()
for _, route := range [...]*InstalledRoute{firstRoute, secondRoute} {
if pendingPackets := table.AddInstalledRoute(defaultRouteKey, route); pendingPackets != nil {
t.Errorf("got table.AddInstalledRoute(%#v, %#v) = %#v, want = false", defaultRouteKey, route, pendingPackets)
t.Errorf("table.AddInstalledRoute(%#v, %#v) = %#v, want = false", defaultRouteKey, route, pendingPackets)
}
// AddInstalledRoute is invoked for the same routeKey two times. Verify
// that the fetched InstalledRoute reflects the most recent invocation of
// AddInstalledRoute.
routeResult, err := table.GetRouteOrInsertPending(defaultRouteKey, pkt)
routeResult, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt)
if err != nil {
t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): %v", defaultRouteKey, pkt, err)
if !hasBufferSpace {
t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): false", defaultRouteKey, pkt)
}
if routeResult.PendingRouteState != PendingRouteStateNone {
t.Errorf("routeResult.PendingRouteState = %s, want = PendingRouteStateNone", routeResult.PendingRouteState)
if routeResult.GetRouteResultState != InstalledRouteFound {
t.Errorf("routeResult.GetRouteResultState = %s, want = InstalledRouteFound", routeResult.GetRouteResultState)
}
if diff := cmp.Diff(route, routeResult.InstalledRoute, cmp.Comparer(installedRouteComparer)); diff != "" {
@@ -363,7 +372,7 @@ func TestRemoveInstalledRoute(t *testing.T) {
t.Fatalf("table.Init(%#v): %s", config, err)
}
route := table.NewInstalledRoute(inputNICID, defaultOutgoingInterfaces)
route := table.NewInstalledRoute(defaultRoute)
table.AddInstalledRoute(defaultRouteKey, route)
@@ -374,10 +383,10 @@ func TestRemoveInstalledRoute(t *testing.T) {
pkt := newPacketBuffer("hello")
defer pkt.DecRef()
result, err := table.GetRouteOrInsertPending(defaultRouteKey, pkt)
result, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt)
if err != nil {
t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): %v", defaultRouteKey, pkt, err)
if !hasBufferSpace {
t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): false", defaultRouteKey, pkt)
}
if result.InstalledRoute != nil {
@@ -444,7 +453,7 @@ func TestSetLastUsedTimestamp(t *testing.T) {
t.Fatalf("table.Init(%#v): %s", config, err)
}
route := table.NewInstalledRoute(inputNICID, defaultOutgoingInterfaces)
route := table.NewInstalledRoute(defaultRoute)
table.AddInstalledRoute(defaultRouteKey, route)
+35
View File
@@ -757,6 +757,41 @@ type NetworkProtocol interface {
Parse(pkt *PacketBuffer) (proto tcpip.TransportProtocolNumber, hasTransportHdr bool, ok bool)
}
// UnicastSourceAndMulticastDestination is a tuple that represents a unicast
// source address and a multicast destination address.
type UnicastSourceAndMulticastDestination struct {
// Source represents a unicast source address.
Source tcpip.Address
// Destination represents a multicast destination address.
Destination tcpip.Address
}
// MulticastRouteOutgoingInterface represents an outgoing interface in a
// multicast route.
type MulticastRouteOutgoingInterface struct {
// ID corresponds to the outgoing NIC.
ID tcpip.NICID
// MinTTL represents the minumum TTL/HopLimit a multicast packet must have to
// be sent through the outgoing interface.
//
// Note: a value of 0 allows all packets to be forwarded.
MinTTL uint8
}
// MulticastRoute is a multicast route.
type MulticastRoute struct {
// ExpectedInputInterface is the interface on which packets using this route
// are expected to ingress.
ExpectedInputInterface tcpip.NICID
// OutgoingInterfaces is the set of interfaces that a multicast packet should
// be forwarded out of.
//
// This field should not be empty.
OutgoingInterfaces []MulticastRouteOutgoingInterface
}
// NetworkDispatcher contains the methods used by the network stack to deliver
// inbound/outbound packets to the appropriate network/packet(if any) endpoints.
type NetworkDispatcher interface {