diff --git a/pkg/tcpip/network/ipv4/ipv4.go b/pkg/tcpip/network/ipv4/ipv4.go index eb7016415..2e310fabc 100644 --- a/pkg/tcpip/network/ipv4/ipv4.go +++ b/pkg/tcpip/network/ipv4/ipv4.go @@ -1521,6 +1521,20 @@ func (p *protocol) AddMulticastRoute(addresses stack.UnicastSourceAndMulticastDe return nil } +// RemoveMulticastRoute implements +// stack.MulticastForwardingNetworkProtocol.RemoveMulticastRoute. +func (p *protocol) RemoveMulticastRoute(addresses stack.UnicastSourceAndMulticastDestination) tcpip.Error { + if err := p.validateUnicastSourceAndMulticastDestination(addresses); err != nil { + return err + } + + if removed := p.multicastRouteTable.RemoveInstalledRoute(addresses); !removed { + return &tcpip.ErrNoRoute{} + } + + return nil +} + func (p *protocol) forwardPendingMulticastPacket(pkt *stack.PacketBuffer, installedRoute *multicast.InstalledRoute) { defer pkt.DecRef() diff --git a/pkg/tcpip/network/ipv6/ipv6.go b/pkg/tcpip/network/ipv6/ipv6.go index bf1f03e53..32808a66d 100644 --- a/pkg/tcpip/network/ipv6/ipv6.go +++ b/pkg/tcpip/network/ipv6/ipv6.go @@ -2320,6 +2320,20 @@ func (p *protocol) AddMulticastRoute(addresses stack.UnicastSourceAndMulticastDe return nil } +// RemoveMulticastRoute implements +// stack.MulticastForwardingNetworkProtocol.RemoveMulticastRoute. +func (p *protocol) RemoveMulticastRoute(addresses stack.UnicastSourceAndMulticastDestination) tcpip.Error { + if err := validateUnicastSourceAndMulticastDestination(addresses); err != nil { + return err + } + + if removed := p.multicastRouteTable.RemoveInstalledRoute(addresses); !removed { + return &tcpip.ErrNoRoute{} + } + + return nil +} + func (p *protocol) forwardPendingMulticastPacket(pkt *stack.PacketBuffer, installedRoute *multicast.InstalledRoute) { defer pkt.DecRef() diff --git a/pkg/tcpip/stack/registration.go b/pkg/tcpip/stack/registration.go index 0282983be..98660e3fd 100644 --- a/pkg/tcpip/stack/registration.go +++ b/pkg/tcpip/stack/registration.go @@ -802,6 +802,13 @@ type MulticastForwardingNetworkProtocol interface { // // Returns an error if the addresses or route is invalid. AddMulticastRoute(UnicastSourceAndMulticastDestination, MulticastRoute) tcpip.Error + + // RemoveMulticastRoute removes the route matching the provided addresses + // from the multicast routing table. + // + // Returns an error if the addresses are invalid or a matching route is not + // found. + RemoveMulticastRoute(UnicastSourceAndMulticastDestination) tcpip.Error } // NetworkDispatcher contains the methods used by the network stack to deliver diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 9abe56a1a..8714f534e 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -564,6 +564,22 @@ func (s *Stack) SetForwardingDefaultAndAllNICs(protocol tcpip.NetworkProtocolNum return nil } +// RemoveMulticastRoute removes a multicast route that matches the specified +// addresses and protocol. +func (s *Stack) RemoveMulticastRoute(protocol tcpip.NetworkProtocolNumber, addresses UnicastSourceAndMulticastDestination) tcpip.Error { + netProto, ok := s.networkProtocols[protocol] + if !ok { + return &tcpip.ErrUnknownProtocol{} + } + + forwardingNetProto, ok := netProto.(MulticastForwardingNetworkProtocol) + if !ok { + return &tcpip.ErrNotSupported{} + } + + return forwardingNetProto.RemoveMulticastRoute(addresses) +} + // AddMulticastRoute adds a multicast route to be used for the specified // addresses and protocol. func (s *Stack) AddMulticastRoute(protocol tcpip.NetworkProtocolNumber, addresses UnicastSourceAndMulticastDestination, route MulticastRoute) tcpip.Error { diff --git a/pkg/tcpip/stack/stack_test.go b/pkg/tcpip/stack/stack_test.go index 1da9054e0..f50386274 100644 --- a/pkg/tcpip/stack/stack_test.go +++ b/pkg/tcpip/stack/stack_test.go @@ -241,7 +241,8 @@ type fakeNetworkProtocol struct { sendPacketCount [10]int defaultTTL uint8 - addMulticastRouteData addMulticastRouteData + addMulticastRouteData addMulticastRouteData + removeMulticastRouteData stack.UnicastSourceAndMulticastDestination } func (*fakeNetworkProtocol) Number() tcpip.NetworkProtocolNumber { @@ -313,6 +314,13 @@ func (f *fakeNetworkProtocol) AddMulticastRoute(addresses stack.UnicastSourceAnd return nil } +// RemoveMulticastRoute implements +// MulticastForwardingNetworkProtocol.RemoveMulticastRoute. +func (f *fakeNetworkProtocol) RemoveMulticastRoute(addresses stack.UnicastSourceAndMulticastDestination) tcpip.Error { + f.removeMulticastRouteData = addresses + return nil +} + // Forwarding implements stack.ForwardingNetworkEndpoint. func (f *fakeNetworkEndpoint) Forwarding() bool { f.mu.RLock() @@ -4740,6 +4748,58 @@ func TestAddMulticastRoute(t *testing.T) { } } +func TestRemoveMulticastRoute(t *testing.T) { + const nicID = 1 + address := testutil.MustParse4("192.168.1.1") + addresses := stack.UnicastSourceAndMulticastDestination{Source: address, Destination: address} + + tests := []struct { + name string + netProto tcpip.NetworkProtocolNumber + factory stack.NetworkProtocolFactory + wantErr tcpip.Error + }{ + { + name: "valid", + netProto: fakeNetNumber, + factory: fakeNetFactory, + wantErr: nil, + }, + { + name: "unknown protocol", + factory: fakeNetFactory, + netProto: arp.ProtocolNumber, + wantErr: &tcpip.ErrUnknownProtocol{}, + }, + { + name: "not supported", + factory: arp.NewProtocol, + netProto: arp.ProtocolNumber, + wantErr: &tcpip.ErrNotSupported{}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := stack.New(stack.Options{ + NetworkProtocols: []stack.NetworkProtocolFactory{test.factory}, + }) + + err := s.RemoveMulticastRoute(test.netProto, addresses) + + if !cmp.Equal(err, test.wantErr, cmpopts.EquateErrors()) { + t.Errorf("s.RemoveMulticastRoute(%d, %#v) = %s, want %s", test.netProto, addresses, err, test.wantErr) + } + + if test.wantErr == nil { + fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol) + if !cmp.Equal(fakeNet.removeMulticastRouteData, addresses) { + t.Errorf("fakeNet.removeMulticastRouteData = %#v, want = %#v", fakeNet.removeMulticastRouteData, addresses) + } + } + }) + } +} + func TestNICForwarding(t *testing.T) { const nicID = 1 diff --git a/pkg/tcpip/tests/integration/multicast_forward_test.go b/pkg/tcpip/tests/integration/multicast_forward_test.go index fc31794a2..78dfcc2da 100644 --- a/pkg/tcpip/tests/integration/multicast_forward_test.go +++ b/pkg/tcpip/tests/integration/multicast_forward_test.go @@ -419,6 +419,170 @@ func TestAddMulticastRoute(t *testing.T) { } } +func TestRemoveMulticastRoute(t *testing.T) { + endpointConfigs := map[tcpip.NICID]endpointAddrType{ + incomingNICID: incomingEndpointAddr, + outgoingNICID: outgoingEndpointAddr, + otherNICID: otherEndpointAddr, + } + + tests := []struct { + name string + srcAddr, dstAddr addrType + wantErr tcpip.Error + }{ + { + name: "success", + srcAddr: remoteUnicastAddr, + dstAddr: multicastAddr, + wantErr: nil, + }, + { + name: "no matching route", + srcAddr: remoteUnicastAddr, + dstAddr: otherMulticastAddr, + wantErr: &tcpip.ErrNoRoute{}, + }, + { + name: "multicast source", + srcAddr: multicastAddr, + dstAddr: multicastAddr, + wantErr: &tcpip.ErrBadAddress{}, + }, + { + name: "any source", + srcAddr: anyAddr, + dstAddr: multicastAddr, + wantErr: &tcpip.ErrBadAddress{}, + }, + { + name: "link-local unicast source", + srcAddr: linkLocalUnicastAddr, + dstAddr: multicastAddr, + wantErr: &tcpip.ErrBadAddress{}, + }, + { + name: "empty source", + srcAddr: emptyAddr, + dstAddr: multicastAddr, + wantErr: &tcpip.ErrBadAddress{}, + }, + { + name: "unicast destination", + srcAddr: remoteUnicastAddr, + dstAddr: remoteUnicastAddr, + wantErr: &tcpip.ErrBadAddress{}, + }, + { + name: "empty destination", + srcAddr: remoteUnicastAddr, + dstAddr: emptyAddr, + wantErr: &tcpip.ErrBadAddress{}, + }, + { + name: "link-local multicast destination", + srcAddr: remoteUnicastAddr, + dstAddr: linkLocalMulticastAddr, + wantErr: &tcpip.ErrBadAddress{}, + }, + } + + 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) { + s := stack.New(stack.Options{ + NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, + TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol}, + }) + defer s.Close() + + endpoints := make(map[tcpip.NICID]*channel.Endpoint) + for nicID, addrType := range endpointConfigs { + ep := channel.New(1, ipv4.MaxTotalSize, "") + defer ep.Close() + + if err := s.CreateNIC(nicID, ep); err != nil { + t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err) + } + addr := tcpip.ProtocolAddress{ + Protocol: protocol, + AddressWithPrefix: getEndpointAddr(protocol, addrType), + } + if err := s.AddProtocolAddress(nicID, addr, stack.AddressProperties{}); err != nil { + t.Fatalf("s.AddProtocolAddress(%d, %#v, {}): %s", nicID, addr, err) + } + s.SetNICMulticastForwarding(nicID, protocol, true /* enabled */) + endpoints[nicID] = ep + } + + srcAddr := getAddr(protocol, remoteUnicastAddr) + dstAddr := getAddr(protocol, multicastAddr) + + outgoingInterfaces := []stack.MulticastRouteOutgoingInterface{ + {ID: outgoingNICID, MinTTL: routeMinTTL}, + } + + addresses := stack.UnicastSourceAndMulticastDestination{ + Source: srcAddr, + Destination: dstAddr, + } + + route := stack.MulticastRoute{ + ExpectedInputInterface: incomingNICID, + OutgoingInterfaces: outgoingInterfaces, + } + + if err := s.AddMulticastRoute(protocol, addresses, route); err != nil { + t.Fatalf("got s.AddMulticastRoute(%d, %#v, %#v) = %s, want = nil", protocol, addresses, route, err) + } + + addresses = stack.UnicastSourceAndMulticastDestination{ + Source: getAddr(protocol, test.srcAddr), + Destination: getAddr(protocol, test.dstAddr), + } + err := s.RemoveMulticastRoute(protocol, addresses) + + if !cmp.Equal(err, test.wantErr, cmpopts.EquateErrors()) { + t.Errorf("got s.RemoveMulticastRoute(%d, %#v) = %s, want %s", protocol, addresses, err, test.wantErr) + } + + incomingEp, ok := endpoints[incomingNICID] + if !ok { + t.Fatalf("got endpoints[%d] = (_, false), want (_, true)", incomingNICID) + } + + injectPacket(incomingEp, protocol, srcAddr, dstAddr, packetTTL) + p := incomingEp.Read() + + if p != nil { + // An ICMP error should never be sent in response to a multicast + // packet. + t.Errorf("expected no ICMP packet through incoming NIC, instead found: %#v", p) + } + + outgoingEp, ok := endpoints[outgoingNICID] + if !ok { + t.Fatalf("got endpoints[%d] = (_, false), want (_, true)", outgoingNICID) + } + + p = outgoingEp.Read() + + // If the route was successfully removed, then the packet should not be + // forwarded. + expectForward := test.wantErr != nil + if (p != nil) != expectForward { + t.Fatalf("got outgoingEp.Read() = %#v, want = (_ == nil) = %t", p, expectForward) + } + + if expectForward { + checkEchoRequest(t, protocol, p, srcAddr, dstAddr, packetTTL-1) + p.DecRef() + } + }) + } + } +} + func TestMulticastForwarding(t *testing.T) { endpointConfigs := map[tcpip.NICID]endpointAddrType{ incomingNICID: incomingEndpointAddr,