From 47fbf57a832139af24d3caa485ec531ad33a23b1 Mon Sep 17 00:00:00 2001 From: Nate Hurley Date: Mon, 4 Apr 2022 10:46:16 -0700 Subject: [PATCH] Add option to enable multicast forwarding Setting this newly added option to "true" will currently be a no-op. Full support for multicast routing will be added in subsequent CLs. Updates #7338. PiperOrigin-RevId: 439356271 --- pkg/tcpip/network/ipv4/ipv4.go | 28 ++++++ pkg/tcpip/network/ipv6/ipv6.go | 28 ++++++ pkg/tcpip/stack/nic.go | 32 +++++++ pkg/tcpip/stack/registration.go | 15 +++ pkg/tcpip/stack/stack.go | 87 +++++++++++++---- pkg/tcpip/stack/stack_test.go | 159 ++++++++++++++++++++++++-------- 6 files changed, 290 insertions(+), 59 deletions(-) diff --git a/pkg/tcpip/network/ipv4/ipv4.go b/pkg/tcpip/network/ipv4/ipv4.go index 609c6e942..3c5e79df8 100644 --- a/pkg/tcpip/network/ipv4/ipv4.go +++ b/pkg/tcpip/network/ipv4/ipv4.go @@ -72,6 +72,7 @@ var ipv4BroadcastAddr = header.IPv4Broadcast.WithPrefix() var _ stack.LinkResolvableNetworkEndpoint = (*endpoint)(nil) var _ stack.ForwardingNetworkEndpoint = (*endpoint)(nil) +var _ stack.MulticastForwardingNetworkEndpoint = (*endpoint)(nil) var _ stack.GroupAddressableEndpoint = (*endpoint)(nil) var _ stack.AddressableEndpoint = (*endpoint)(nil) var _ stack.NetworkEndpoint = (*endpoint)(nil) @@ -94,6 +95,15 @@ type endpoint struct { // +checkatomic forwarding uint32 + // multicastForwarding is set to forwardingEnabled when the endpoint has + // forwarding enabled and forwardingDisabled when it is disabled. + // + // TODO(https://gvisor.dev/issue/7338): Implement support for multicast + //forwarding. Currently, setting this value to true is a no-op. + // + // +checkatomic + multicastForwarding uint32 + // mu protects below. mu sync.RWMutex @@ -236,6 +246,24 @@ func (e *endpoint) SetForwarding(forwarding bool) bool { return prevForwarding } +// MulticastForwarding implements stack.MulticastForwardingNetworkEndpoint. +func (e *endpoint) MulticastForwarding() bool { + return atomic.LoadUint32(&e.multicastForwarding) == forwardingEnabled +} + +// SetMulticastForwarding implements stack.MulticastForwardingNetworkEndpoint. +func (e *endpoint) SetMulticastForwarding(forwarding bool) bool { + e.mu.Lock() + defer e.mu.Unlock() + + updatedForwarding := uint32(forwardingDisabled) + if forwarding { + updatedForwarding = forwardingEnabled + } + + return atomic.SwapUint32(&e.multicastForwarding, updatedForwarding) != forwardingDisabled +} + // Enable implements stack.NetworkEndpoint. func (e *endpoint) Enable() tcpip.Error { e.mu.Lock() diff --git a/pkg/tcpip/network/ipv6/ipv6.go b/pkg/tcpip/network/ipv6/ipv6.go index ebc85fa52..ca7e7ddd3 100644 --- a/pkg/tcpip/network/ipv6/ipv6.go +++ b/pkg/tcpip/network/ipv6/ipv6.go @@ -174,6 +174,7 @@ var _ stack.DuplicateAddressDetector = (*endpoint)(nil) var _ stack.LinkAddressResolver = (*endpoint)(nil) var _ stack.LinkResolvableNetworkEndpoint = (*endpoint)(nil) var _ stack.ForwardingNetworkEndpoint = (*endpoint)(nil) +var _ stack.MulticastForwardingNetworkEndpoint = (*endpoint)(nil) var _ stack.GroupAddressableEndpoint = (*endpoint)(nil) var _ stack.AddressableEndpoint = (*endpoint)(nil) var _ stack.NetworkEndpoint = (*endpoint)(nil) @@ -198,6 +199,15 @@ type endpoint struct { // Must be accessed using atomic operations. forwarding uint32 + // multicastForwarding is set to forwardingEnabled when the endpoint has + // forwarding enabled and forwardingDisabled when it is disabled. + // + // TODO(https://gvisor.dev/issue/7338): Implement support for multicast + // forwarding. Currently, setting this value to true is a no-op. + // + // Must be accessed using atomic operations. + multicastForwarding uint32 + mu struct { sync.RWMutex @@ -505,6 +515,24 @@ func (e *endpoint) SetForwarding(forwarding bool) bool { return prevForwarding } +// MulticastForwarding implements stack.MulticastForwardingNetworkEndpoint. +func (e *endpoint) MulticastForwarding() bool { + return atomic.LoadUint32(&e.multicastForwarding) == forwardingEnabled +} + +// SetMulticastForwarding implements stack.MulticastForwardingNetworkEndpoint. +func (e *endpoint) SetMulticastForwarding(forwarding bool) bool { + e.mu.Lock() + defer e.mu.Unlock() + + updatedForwarding := uint32(forwardingDisabled) + if forwarding { + updatedForwarding = forwardingEnabled + } + + return atomic.SwapUint32(&e.multicastForwarding, updatedForwarding) != forwardingDisabled +} + // Enable implements stack.NetworkEndpoint. func (e *endpoint) Enable() tcpip.Error { e.mu.Lock() diff --git a/pkg/tcpip/stack/nic.go b/pkg/tcpip/stack/nic.go index 163b08e08..fa1a66f52 100644 --- a/pkg/tcpip/stack/nic.go +++ b/pkg/tcpip/stack/nic.go @@ -1040,3 +1040,35 @@ func (n *nic) forwarding(protocol tcpip.NetworkProtocolNumber) (bool, tcpip.Erro return forwardingEP.Forwarding(), nil } + +func (n *nic) multicastForwardingEndpoint(protocol tcpip.NetworkProtocolNumber) (MulticastForwardingNetworkEndpoint, tcpip.Error) { + ep := n.getNetworkEndpoint(protocol) + if ep == nil { + return nil, &tcpip.ErrUnknownProtocol{} + } + + forwardingEP, ok := ep.(MulticastForwardingNetworkEndpoint) + if !ok { + return nil, &tcpip.ErrNotSupported{} + } + + return forwardingEP, nil +} + +func (n *nic) setMulticastForwarding(protocol tcpip.NetworkProtocolNumber, enable bool) (bool, tcpip.Error) { + ep, err := n.multicastForwardingEndpoint(protocol) + if err != nil { + return false, err + } + + return ep.SetMulticastForwarding(enable), nil +} + +func (n *nic) multicastForwarding(protocol tcpip.NetworkProtocolNumber) (bool, tcpip.Error) { + ep, err := n.multicastForwardingEndpoint(protocol) + if err != nil { + return false, err + } + + return ep.MulticastForwarding(), nil +} diff --git a/pkg/tcpip/stack/registration.go b/pkg/tcpip/stack/registration.go index a5f691a73..72ed62621 100644 --- a/pkg/tcpip/stack/registration.go +++ b/pkg/tcpip/stack/registration.go @@ -691,6 +691,21 @@ type ForwardingNetworkEndpoint interface { SetForwarding(bool) bool } +// MulticastForwardingNetworkEndpoint is a network endpoint that may forward +// multicast packets. +type MulticastForwardingNetworkEndpoint interface { + ForwardingNetworkEndpoint + + // MulticastForwarding returns true if multicast forwarding is enabled. + // Otherwise, returns false. + MulticastForwarding() bool + + // SetMulticastForwarding sets the multicast forwarding configuration. + // + // Returns the previous forwarding configuration. + SetMulticastForwarding(bool) bool +} + // NetworkProtocol is the interface that needs to be implemented by network // protocols (e.g., ipv4, ipv6) that want to be part of the networking stack. type NetworkProtocol interface { diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index b8120d754..fec566e4e 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -563,6 +563,40 @@ func (s *Stack) SetForwardingDefaultAndAllNICs(protocol tcpip.NetworkProtocolNum return nil } +// SetNICMulticastForwarding enables or disables multicast packet forwarding on +// the specified NIC for the passed protocol. +// +// Returns the previous configuration on the NIC. +// +// TODO(https://gvisor.dev/issue/7338): Implement support for multicast +// forwarding. Currently, setting this value is a no-op and is not ready for +// use. +func (s *Stack) SetNICMulticastForwarding(id tcpip.NICID, protocol tcpip.NetworkProtocolNumber, enable bool) (bool, tcpip.Error) { + s.mu.RLock() + defer s.mu.RUnlock() + + nic, ok := s.nics[id] + if !ok { + return false, &tcpip.ErrUnknownNICID{} + } + + return nic.setMulticastForwarding(protocol, enable) +} + +// NICMulticastForwarding returns the multicast forwarding configuration for +// the specified NIC. +func (s *Stack) NICMulticastForwarding(id tcpip.NICID, protocol tcpip.NetworkProtocolNumber) (bool, tcpip.Error) { + s.mu.RLock() + defer s.mu.RUnlock() + + nic, ok := s.nics[id] + if !ok { + return false, &tcpip.ErrUnknownNICID{} + } + + return nic.multicastForwarding(protocol) +} + // PortRange returns the UDP and TCP inclusive range of ephemeral ports used in // both IPv4 and IPv6. func (s *Stack) PortRange() (uint16, uint16) { @@ -843,6 +877,10 @@ type NICInfo struct { // Forwarding holds the forwarding status for each network endpoint that // supports forwarding. Forwarding map[tcpip.NetworkProtocolNumber]bool + + // MulticastForwarding holds the forwarding status for each network endpoint + // that supports multicast forwarding. + MulticastForwarding map[tcpip.NetworkProtocolNumber]bool } // HasNIC returns true if the NICID is defined in the stack. @@ -858,6 +896,21 @@ func (s *Stack) NICInfo() map[tcpip.NICID]NICInfo { s.mu.RLock() defer s.mu.RUnlock() + type forwardingFn func(tcpip.NetworkProtocolNumber) (bool, tcpip.Error) + forwardingValue := func(forwardingFn forwardingFn, proto tcpip.NetworkProtocolNumber, nicID tcpip.NICID, fnName string) (forward bool, ok bool) { + switch forwarding, err := forwardingFn(proto); err.(type) { + case nil: + return forwarding, true + case *tcpip.ErrUnknownProtocol: + panic(fmt.Sprintf("expected network protocol %d to be available on NIC %d", proto, nicID)) + case *tcpip.ErrNotSupported: + // Not all network protocols support forwarding. + default: + panic(fmt.Sprintf("nic(id=%d).%s(%d): %s", nicID, fnName, proto, err)) + } + return false, false + } + nics := make(map[tcpip.NICID]NICInfo) for id, nic := range s.nics { flags := NICStateFlags{ @@ -873,28 +926,26 @@ func (s *Stack) NICInfo() map[tcpip.NICID]NICInfo { } info := NICInfo{ - Name: nic.name, - LinkAddress: nic.NetworkLinkEndpoint.LinkAddress(), - ProtocolAddresses: nic.primaryAddresses(), - Flags: flags, - MTU: nic.NetworkLinkEndpoint.MTU(), - Stats: nic.stats.local, - NetworkStats: netStats, - Context: nic.context, - ARPHardwareType: nic.NetworkLinkEndpoint.ARPHardwareType(), - Forwarding: make(map[tcpip.NetworkProtocolNumber]bool), + Name: nic.name, + LinkAddress: nic.NetworkLinkEndpoint.LinkAddress(), + ProtocolAddresses: nic.primaryAddresses(), + Flags: flags, + MTU: nic.NetworkLinkEndpoint.MTU(), + Stats: nic.stats.local, + NetworkStats: netStats, + Context: nic.context, + ARPHardwareType: nic.NetworkLinkEndpoint.ARPHardwareType(), + Forwarding: make(map[tcpip.NetworkProtocolNumber]bool), + MulticastForwarding: make(map[tcpip.NetworkProtocolNumber]bool), } for proto := range s.networkProtocols { - switch forwarding, err := nic.forwarding(proto); err.(type) { - case nil: + if forwarding, ok := forwardingValue(nic.forwarding, proto, id, "forwarding"); ok { info.Forwarding[proto] = forwarding - case *tcpip.ErrUnknownProtocol: - panic(fmt.Sprintf("expected network protocol %d to be available on NIC %d", proto, nic.ID())) - case *tcpip.ErrNotSupported: - // Not all network protocols support forwarding. - default: - panic(fmt.Sprintf("nic(id=%d).forwarding(%d): %s", nic.ID(), proto, err)) + } + + if multicastForwarding, ok := forwardingValue(nic.multicastForwarding, proto, id, "multicastForwarding"); ok { + info.MulticastForwarding[proto] = multicastForwarding } } diff --git a/pkg/tcpip/stack/stack_test.go b/pkg/tcpip/stack/stack_test.go index 763260c9b..4ef23c7b2 100644 --- a/pkg/tcpip/stack/stack_test.go +++ b/pkg/tcpip/stack/stack_test.go @@ -84,8 +84,9 @@ type fakeNetworkEndpoint struct { mu struct { sync.RWMutex - enabled bool - forwarding bool + enabled bool + forwarding bool + multicastForwarding bool } nic stack.NetworkInterface @@ -313,6 +314,22 @@ func (f *fakeNetworkEndpoint) SetForwarding(v bool) bool { return prev } +// MulticastForwarding implements stack.MulticastForwardingNetworkEndpoint. +func (f *fakeNetworkEndpoint) MulticastForwarding() bool { + f.mu.RLock() + defer f.mu.RUnlock() + return f.mu.multicastForwarding +} + +// SetMulticastForwarding implements stack.MulticastForwardingNetworkEndpoint. +func (f *fakeNetworkEndpoint) SetMulticastForwarding(v bool) bool { + f.mu.Lock() + defer f.mu.Unlock() + prev := f.mu.multicastForwarding + f.mu.multicastForwarding = v + return prev +} + func fakeNetFactory(s *stack.Stack) stack.NetworkProtocol { return &fakeNetworkProtocol{stack: s} } @@ -4670,50 +4687,110 @@ func TestNICForwarding(t *testing.T) { }, } + subTests := []struct { + name string + getForwardingFunc func(*stack.Stack, tcpip.NICID, tcpip.NetworkProtocolNumber) (bool, tcpip.Error) + getForwardingFuncName string + setForwardingFunc func(*stack.Stack, tcpip.NICID, tcpip.NetworkProtocolNumber, bool) (bool, tcpip.Error) + setForwardingFuncName string + getNicInfoForwardingMap func(stack.NICInfo) map[tcpip.NetworkProtocolNumber]bool + nicInfoForwardingMapName string + }{ + { + name: "unicast", + getForwardingFunc: (*stack.Stack).NICForwarding, + getForwardingFuncName: "NICForwarding", + setForwardingFunc: (*stack.Stack).SetNICForwarding, + setForwardingFuncName: "SetNICForwarding", + getNicInfoForwardingMap: func(info stack.NICInfo) map[tcpip.NetworkProtocolNumber]bool { return info.Forwarding }, + nicInfoForwardingMapName: "Forwarding", + }, + { + name: "multicast", + getForwardingFunc: (*stack.Stack).NICMulticastForwarding, + getForwardingFuncName: "NICMulticastForwarding", + setForwardingFunc: (*stack.Stack).SetNICMulticastForwarding, + setForwardingFuncName: "SetNICMulticastForwarding", + getNicInfoForwardingMap: func(info stack.NICInfo) map[tcpip.NetworkProtocolNumber]bool { return info.MulticastForwarding }, + nicInfoForwardingMapName: "MulticastForwarding", + }, + } + for _, test := range tests { t.Run(test.name, func(t *testing.T) { - s := stack.New(stack.Options{ - NetworkProtocols: []stack.NetworkProtocolFactory{test.factory}, - }) - if err := s.CreateNIC(nicID, channel.New(0, defaultMTU, "")); err != nil { - t.Fatalf("CreateNIC(%d, _): %s", nicID, err) - } + for _, subTest := range subTests { + t.Run(subTest.name, func(t *testing.T) { + s := stack.New(stack.Options{ + NetworkProtocols: []stack.NetworkProtocolFactory{test.factory}, + }) + if err := s.CreateNIC(nicID, channel.New(0, defaultMTU, "")); err != nil { + t.Fatalf("CreateNIC(%d, _): %s", nicID, err) + } - // Forwarding should initially be disabled. - if forwarding, err := s.NICForwarding(nicID, test.netProto); err != nil { - t.Fatalf("s.NICForwarding(%d, %d): %s", nicID, test.netProto, err) - } else if forwarding { - t.Errorf("got s.NICForwarding(%d, %d) = true, want = false", nicID, test.netProto) - } + // Forwarding should initially be disabled. + if forwarding, err := subTest.getForwardingFunc(s, nicID, test.netProto); err != nil { + t.Fatalf("s.%s(%d, %d): %s", subTest.getForwardingFuncName, nicID, test.netProto, err) + } else if forwarding { + t.Errorf("got s.%s(%d, %d) = true, want = false", subTest.getForwardingFuncName, nicID, test.netProto) + } - // Setting forwarding to be enabled should return the previous - // configuration of false. Enabling it a second time should be a no-op. - for _, wantPrevForwarding := range [...]bool{false, true} { - if prevForwarding, err := s.SetNICForwarding(nicID, test.netProto, true); err != nil { - t.Fatalf("s.SetNICForwarding(%d, %d, true): %s", nicID, test.netProto, err) - } else if prevForwarding != wantPrevForwarding { - t.Errorf("got s.SetNICForwarding(%d, %d, true) = %t, want = %t", nicID, test.netProto, prevForwarding, wantPrevForwarding) - } - if forwarding, err := s.NICForwarding(nicID, test.netProto); err != nil { - t.Fatalf("s.NICForwarding(%d, %d): %s", nicID, test.netProto, err) - } else if !forwarding { - t.Errorf("got s.NICForwarding(%d, %d) = false, want = true", nicID, test.netProto) - } - } + // Setting forwarding to be enabled should return the previous + // configuration of false. Enabling it a second time should be a + // no-op. + for _, wantPrevForwarding := range [...]bool{false, true} { + if prevForwarding, err := subTest.setForwardingFunc(s, nicID, test.netProto, true); err != nil { + t.Fatalf("s.%s(%d, %d, true): %s", subTest.setForwardingFuncName, nicID, test.netProto, err) + } else if prevForwarding != wantPrevForwarding { + t.Errorf("got s.%s(%d, %d, true) = %t, want = %t", subTest.setForwardingFuncName, nicID, test.netProto, prevForwarding, wantPrevForwarding) + } + if forwarding, err := subTest.getForwardingFunc(s, nicID, test.netProto); err != nil { + t.Fatalf("s.%s(%d, %d): %s", subTest.getForwardingFuncName, nicID, test.netProto, err) + } else if !forwarding { + t.Errorf("got s.%s(%d, %d) = false, want = true", subTest.getForwardingFuncName, nicID, test.netProto) + } + // Verify that the NICInfo also contains the expected value. + allNICInfo := s.NICInfo() + if info, ok := allNICInfo[nicID]; !ok { + t.Fatalf("entry for %d missing from allNICInfo = %+v", nicID, allNICInfo) + } else { + forwardingMap := subTest.getNicInfoForwardingMap(info) + if forward, ok := forwardingMap[test.netProto]; !ok { + t.Fatalf("entry for %d missing from info.%s = %+v", test.netProto, subTest.nicInfoForwardingMapName, forwardingMap) + } else if !forward { + t.Errorf("got info.%s[%d] = %t, want = true", subTest.nicInfoForwardingMapName, test.netProto, forward) + } + } + } - // Setting forwarding to be disabled should return the previous - // configuration of true. Disabling it a second time should be a no-op. - for _, wantPrevForwarding := range [...]bool{true, false} { - if prevForwarding, err := s.SetNICForwarding(nicID, test.netProto, false); err != nil { - t.Fatalf("s.SetNICForwarding(%d, %d, false): %s", nicID, test.netProto, err) - } else if prevForwarding != wantPrevForwarding { - t.Errorf("got s.SetNICForwarding(%d, %d, false) = %t, want = %t", nicID, test.netProto, prevForwarding, wantPrevForwarding) - } - if forwarding, err := s.NICForwarding(nicID, test.netProto); err != nil { - t.Fatalf("s.NICForwarding(%d, %d): %s", nicID, test.netProto, err) - } else if forwarding { - t.Errorf("got s.NICForwarding(%d, %d) = true, want = false", nicID, test.netProto) - } + // Setting forwarding to be disabled should return the previous + // configuration of true. Disabling it a second time should be a + // no-op. + for _, wantPrevForwarding := range [...]bool{true, false} { + if prevForwarding, err := subTest.setForwardingFunc(s, nicID, test.netProto, false); err != nil { + t.Fatalf("s.%s(%d, %d, false): %s", subTest.setForwardingFuncName, nicID, test.netProto, err) + } else if prevForwarding != wantPrevForwarding { + t.Errorf("got s.%s(%d, %d, false) = %t, want = %t", subTest.setForwardingFuncName, nicID, test.netProto, prevForwarding, wantPrevForwarding) + } + if forwarding, err := subTest.getForwardingFunc(s, nicID, test.netProto); err != nil { + t.Fatalf("s.%s(%d, %d): %s", subTest.getForwardingFuncName, nicID, test.netProto, err) + } else if forwarding { + t.Errorf("got s.%s(%d, %d) = true, want = false", subTest.getForwardingFuncName, nicID, test.netProto) + } + // Verify that the NICInfo also contains the expected value. + allNICInfo := s.NICInfo() + if info, ok := allNICInfo[nicID]; !ok { + t.Fatalf("entry for %d missing from allNICInfo = %+v", nicID, allNICInfo) + } else { + forwardingMap := subTest.getNicInfoForwardingMap(info) + if forward, ok := forwardingMap[test.netProto]; !ok { + t.Fatalf("entry for %d missing from info.%s = %+v", test.netProto, subTest.nicInfoForwardingMapName, forwardingMap) + } else if forward { + t.Errorf("got info.%s[%d] = %t, want = false", subTest.nicInfoForwardingMapName, test.netProto, forward) + } + } + } + + }) } }) }