Emit multicast forwarding events.

Updates #7338.

PiperOrigin-RevId: 452051657
This commit is contained in:
Nate Hurley
2022-05-31 08:24:53 -07:00
committed by gVisor bot
parent c7690e05c1
commit ea5c64b617
5 changed files with 191 additions and 26 deletions
+31 -4
View File
@@ -367,6 +367,18 @@ func (e *endpoint) disableLocked() {
}
}
// multicastEventDispatcher returns the multicast forwarding event dispatcher.
//
// Panics if a multicast forwarding event dispatcher does not exist. This
// indicates that multicast forwarding is enabled, but no dispatcher was
// provided.
func (e *endpoint) multicastEventDispatcher() stack.MulticastForwardingEventDispatcher {
if mcastDisp := e.protocol.options.MulticastForwardingDisp; mcastDisp != nil {
return mcastDisp
}
panic("e.procotol.options.MulticastForwardingDisp unexpectedly nil")
}
// DefaultTTL is the default time-to-live value for this endpoint.
func (e *endpoint) DefaultTTL() uint8 {
return e.protocol.DefaultTTL()
@@ -886,10 +898,18 @@ func (e *endpoint) forwardMulticastPacket(h header.IPv4, pkt *stack.PacketBuffer
return &ip.ErrNoMulticastPendingQueueBufferSpace{}
}
// TODO(https://gvisor.dev/issue/7338): Emit an event for a missing route.
if result.GetRouteResultState == multicast.InstalledRouteFound {
switch result.GetRouteResultState {
case multicast.InstalledRouteFound:
// Attempt to forward the pkt using an existing route.
return e.forwardValidatedMulticastPacket(pkt, result.InstalledRoute)
case multicast.NoRouteFoundAndPendingInserted:
e.multicastEventDispatcher().OnMissingRoute(stack.MulticastPacketContext{
stack.UnicastSourceAndMulticastDestination{h.SourceAddress(), h.DestinationAddress()},
e.nic.ID(),
})
case multicast.PacketQueuedInPendingRoute:
default:
panic(fmt.Sprintf("unexpected GetRouteResultState: %s", result.GetRouteResultState))
}
return &ip.ErrNoRoute{}
}
@@ -937,8 +957,11 @@ func (e *endpoint) forwardValidatedMulticastPacket(pkt *stack.PacketBuffer, inst
// on the proper interface for forwarding. If not, the datagram is
// dropped silently.
if e.nic.ID() != installedRoute.ExpectedInputInterface {
// TODO(https://gvisor.dev/issue/7338): Emit an event for an unexpected
// input interface.
h := header.IPv4(pkt.NetworkHeader().View())
e.multicastEventDispatcher().OnUnexpectedInputInterface(stack.MulticastPacketContext{
stack.UnicastSourceAndMulticastDestination{h.SourceAddress(), h.DestinationAddress()},
e.nic.ID(),
}, installedRoute.ExpectedInputInterface)
return &ip.ErrUnexpectedMulticastInputInterface{}
}
@@ -1749,6 +1772,10 @@ type Options struct {
// AllowExternalLoopbackTraffic indicates that inbound loopback packets (i.e.
// martian loopback packets) should be accepted.
AllowExternalLoopbackTraffic bool
// MulticastForwardingDisp is the multicast forwarding event dispatcher that
// an integrator can provide to receive multicast forwarding events.
MulticastForwardingDisp stack.MulticastForwardingEventDispatcher
}
// NewProtocolWithOptions returns an IPv4 network protocol.
+10 -1
View File
@@ -199,6 +199,15 @@ func handleICMPInIPv6(ep stack.NetworkEndpoint, src, dst tcpip.Address, icmp hea
pkt.DecRef()
}
var _ stack.MulticastForwardingEventDispatcher = (*fakeMulticastEventDispatcher)(nil)
type fakeMulticastEventDispatcher struct{}
func (m *fakeMulticastEventDispatcher) OnMissingRoute(context stack.MulticastPacketContext) {}
func (m *fakeMulticastEventDispatcher) OnUnexpectedInputInterface(context stack.MulticastPacketContext, expectedInputInterface tcpip.NICID) {
}
type testContext struct {
s *stack.Stack
clock *faketime.ManualClock
@@ -207,7 +216,7 @@ type testContext struct {
func newTestContext() testContext {
clock := faketime.NewManualClock()
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocolWithOptions(Options{MulticastForwardingDisp: &fakeMulticastEventDispatcher{}})},
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6, udp.NewProtocol},
Clock: clock,
})
+26 -4
View File
@@ -1123,15 +1123,18 @@ func (e *endpoint) forwardMulticastPacket(h header.IPv6, pkt *stack.PacketBuffer
return &ip.ErrNoMulticastPendingQueueBufferSpace{}
}
// TODO(https://gvisor.dev/issue/7338): Emit an event for a missing route.
switch result.GetRouteResultState {
case multicast.InstalledRouteFound:
// Attempt to forward the pkt using an existing route.
return e.forwardValidatedMulticastPacket(pkt, result.InstalledRoute)
case multicast.NoRouteFoundAndPendingInserted:
e.multicastEventDispatcher().OnMissingRoute(stack.MulticastPacketContext{
stack.UnicastSourceAndMulticastDestination{h.SourceAddress(), h.DestinationAddress()},
e.nic.ID(),
})
case multicast.PacketQueuedInPendingRoute:
default:
panic(fmt.Sprintf("unexpected result.GetRouteResultState: %s", result.GetRouteResultState))
panic(fmt.Sprintf("unexpected GetRouteResultState: %s", result.GetRouteResultState))
}
return &ip.ErrNoRoute{}
}
@@ -1148,8 +1151,11 @@ func (e *endpoint) forwardValidatedMulticastPacket(pkt *stack.PacketBuffer, inst
// on the proper interface for forwarding. If not, the datagram is
// dropped silently.
if e.nic.ID() != installedRoute.ExpectedInputInterface {
// TODO(https://gvisor.dev/issue/7338): Emit an event for an unexpected
// input interface.
h := header.IPv6(pkt.NetworkHeader().View())
e.multicastEventDispatcher().OnUnexpectedInputInterface(stack.MulticastPacketContext{
stack.UnicastSourceAndMulticastDestination{h.SourceAddress(), h.DestinationAddress()},
e.nic.ID(),
}, installedRoute.ExpectedInputInterface)
return &ip.ErrUnexpectedMulticastInputInterface{}
}
@@ -2264,6 +2270,18 @@ func (p *protocol) DefaultTTL() uint8 {
return uint8(p.defaultTTL.Load())
}
// multicastEventDispatcher returns the multicast forwarding event dispatcher.
//
// Panics if a multicast forwarding event dispatcher does not exist. This
// indicates that multicast forwarding is enabled, but no dispatcher was
// provided.
func (e *endpoint) multicastEventDispatcher() stack.MulticastForwardingEventDispatcher {
if mcastDisp := e.protocol.options.MulticastForwardingDisp; mcastDisp != nil {
return mcastDisp
}
panic("e.procotol.options.MulticastForwardingDisp unexpectedly nil")
}
// Close implements stack.TransportProtocol.
func (p *protocol) Close() {
p.fragmentation.Release()
@@ -2528,6 +2546,10 @@ type Options struct {
// AllowExternalLoopbackTraffic indicates that inbound loopback packets (i.e.
// martian loopback packets) should be accepted.
AllowExternalLoopbackTraffic bool
// MulticastForwardingDisp is the multicast forwarding event dispatcher that
// an integrator can provide to receive multicast forwarding events.
MulticastForwardingDisp stack.MulticastForwardingEventDispatcher
}
// NewProtocolWithOptions returns an IPv6 network protocol.
+31
View File
@@ -819,6 +819,37 @@ type MulticastForwardingNetworkProtocol interface {
MulticastRouteLastUsedTime(UnicastSourceAndMulticastDestination) (tcpip.MonotonicTime, tcpip.Error)
}
// MulticastPacketContext is the context in which a multicast packet triggered
// a multicast forwarding event.
type MulticastPacketContext struct {
// SourceAndDestination contains the unicast source address and the multicast
// destination address found in the relevant multicast packet.
SourceAndDestination UnicastSourceAndMulticastDestination
// InputInterface is the interface on which the relevant multicast packet
// arrived.
InputInterface tcpip.NICID
}
// MulticastForwardingEventDispatcher is the interface that integrators should
// implement to handle multicast routing events.
type MulticastForwardingEventDispatcher interface {
// OnMissingRoute is called when an incoming multicast packet does not match
// any installed route.
//
// The packet that triggered this event may be queued so that it can be
// transmitted once a route is installed. Even then, it may still be dropped
// as per the routing table's GC/eviction policy.
OnMissingRoute(MulticastPacketContext)
// OnUnexpectedInputInterface is called when a multicast packet arrives at an
// interface that does not match the installed route's expected input
// interface.
//
// This may be an indication of a routing loop. The packet that triggered
// this event is dropped without being forwarded.
OnUnexpectedInputInterface(context MulticastPacketContext, expectedInputInterface tcpip.NICID)
}
// 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 {
@@ -68,6 +68,33 @@ const (
otherOutgoingEndpointAddr
)
type onMissingRouteData struct {
context stack.MulticastPacketContext
}
type onUnexpectedInputInterfaceData struct {
context stack.MulticastPacketContext
expectedInputInterface tcpip.NICID
}
var _ stack.MulticastForwardingEventDispatcher = (*fakeMulticastEventDispatcher)(nil)
type fakeMulticastEventDispatcher struct {
onMissingRouteData *onMissingRouteData
onUnexpectedInputInterfaceData *onUnexpectedInputInterfaceData
}
func (m *fakeMulticastEventDispatcher) OnMissingRoute(context stack.MulticastPacketContext) {
m.onMissingRouteData = &onMissingRouteData{context}
}
func (m *fakeMulticastEventDispatcher) OnUnexpectedInputInterface(context stack.MulticastPacketContext, expectedInputInterface tcpip.NICID) {
m.onUnexpectedInputInterfaceData = &onUnexpectedInputInterfaceData{
context,
expectedInputInterface,
}
}
var (
v4Addrs = map[addrType]tcpip.Address{
anyAddr: header.IPv4Any,
@@ -335,9 +362,12 @@ func TestAddMulticastRoute(t *testing.T) {
for _, test := range tests {
for _, protocol := range []tcpip.NetworkProtocolNumber{ipv4.ProtocolNumber, ipv6.ProtocolNumber} {
t.Run(fmt.Sprintf("%s %d", test.name, protocol), func(t *testing.T) {
eventDispatcher := &fakeMulticastEventDispatcher{}
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
NetworkProtocols: []stack.NetworkProtocolFactory{
ipv4.NewProtocolWithOptions(ipv4.Options{MulticastForwardingDisp: eventDispatcher}),
ipv6.NewProtocolWithOptions(ipv6.Options{MulticastForwardingDisp: eventDispatcher}),
},
})
defer s.Close()
@@ -646,8 +676,12 @@ func TestRemoveMulticastRoute(t *testing.T) {
for _, test := range tests {
for _, protocol := range []tcpip.NetworkProtocolNumber{ipv4.ProtocolNumber, ipv6.ProtocolNumber} {
t.Run(fmt.Sprintf("%s %d", test.name, protocol), func(t *testing.T) {
eventDispatcher := &fakeMulticastEventDispatcher{}
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
NetworkProtocols: []stack.NetworkProtocolFactory{
ipv4.NewProtocolWithOptions(ipv4.Options{MulticastForwardingDisp: eventDispatcher}),
ipv6.NewProtocolWithOptions(ipv6.Options{MulticastForwardingDisp: eventDispatcher}),
},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
})
defer s.Close()
@@ -757,14 +791,16 @@ func TestMulticastForwarding(t *testing.T) {
}
tests := []struct {
name string
dstAddr addrType
ttl uint8
routeInputInterface tcpip.NICID
disableMulticastForwarding bool
removeOutputInterface tcpip.NICID
joinMulticastGroup bool
expectedForwardingInterfaces []tcpip.NICID
name string
dstAddr addrType
ttl uint8
routeInputInterface tcpip.NICID
disableMulticastForwarding bool
removeOutputInterface tcpip.NICID
expectMissingRouteEvent bool
expectUnexpectedInputInterfaceEvent bool
joinMulticastGroup bool
expectedForwardingInterfaces []tcpip.NICID
}{
{
name: "forward only",
@@ -798,11 +834,12 @@ func TestMulticastForwarding(t *testing.T) {
expectedForwardingInterfaces: []tcpip.NICID{},
},
{
name: "unexpected input interface",
dstAddr: multicastAddr,
ttl: packetTTL,
routeInputInterface: otherNICID,
expectedForwardingInterfaces: []tcpip.NICID{},
name: "unexpected input interface",
dstAddr: multicastAddr,
ttl: packetTTL,
routeInputInterface: otherNICID,
expectUnexpectedInputInterfaceEvent: true,
expectedForwardingInterfaces: []tcpip.NICID{},
},
{
name: "output interface removed",
@@ -838,15 +875,27 @@ func TestMulticastForwarding(t *testing.T) {
dstAddr: otherMulticastAddr,
ttl: packetTTL,
routeInputInterface: incomingNICID,
expectMissingRouteEvent: true,
expectedForwardingInterfaces: []tcpip.NICID{},
},
}
for _, test := range tests {
for _, protocol := range []tcpip.NetworkProtocolNumber{ipv4.ProtocolNumber, ipv6.ProtocolNumber} {
ipv4EventDispatcher := &fakeMulticastEventDispatcher{}
ipv6EventDispatcher := &fakeMulticastEventDispatcher{}
eventDispatchers := map[tcpip.NetworkProtocolNumber]*fakeMulticastEventDispatcher{
ipv4.ProtocolNumber: ipv4EventDispatcher,
ipv6.ProtocolNumber: ipv6EventDispatcher,
}
t.Run(fmt.Sprintf("%s %d", test.name, protocol), func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
NetworkProtocols: []stack.NetworkProtocolFactory{
ipv4.NewProtocolWithOptions(ipv4.Options{MulticastForwardingDisp: ipv4EventDispatcher}),
ipv6.NewProtocolWithOptions(ipv6.Options{MulticastForwardingDisp: ipv6EventDispatcher}),
},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
})
defer s.Close()
@@ -974,6 +1023,33 @@ func TestMulticastForwarding(t *testing.T) {
checkEchoReply(t, protocol, p, getEndpointAddr(protocol, incomingEpAddrType).Address, srcAddr)
p.DecRef()
}
eventDispatcher, ok := eventDispatchers[protocol]
if !ok {
t.Fatalf("eventDispatchers[%d] = (_, false), want (_, true)", protocol)
}
wantUnexpectedInputInterfaceEvent := func() *onUnexpectedInputInterfaceData {
if test.expectUnexpectedInputInterfaceEvent {
return &onUnexpectedInputInterfaceData{stack.MulticastPacketContext{stack.UnicastSourceAndMulticastDestination{srcAddr, dstAddr}, incomingNICID}, test.routeInputInterface}
}
return nil
}()
if diff := cmp.Diff(wantUnexpectedInputInterfaceEvent, eventDispatcher.onUnexpectedInputInterfaceData, cmp.AllowUnexported(onUnexpectedInputInterfaceData{})); diff != "" {
t.Errorf("onUnexpectedInputInterfaceData mismatch (-want +got):\n%s", diff)
}
wantMissingRouteEvent := func() *onMissingRouteData {
if test.expectMissingRouteEvent {
return &onMissingRouteData{stack.MulticastPacketContext{stack.UnicastSourceAndMulticastDestination{srcAddr, dstAddr}, incomingNICID}}
}
return nil
}()
if diff := cmp.Diff(wantMissingRouteEvent, eventDispatcher.onMissingRouteData, cmp.AllowUnexported(onMissingRouteData{})); diff != "" {
t.Errorf("onMissingRouteData mismatch (-want +got):\n%s", diff)
}
})
}
}