diff --git a/pkg/sentry/socket/netstack/stack.go b/pkg/sentry/socket/netstack/stack.go index 3a1ef8e51..bd81375c3 100644 --- a/pkg/sentry/socket/netstack/stack.go +++ b/pkg/sentry/socket/netstack/stack.go @@ -165,6 +165,21 @@ func (s *Stack) SetInterface(ctx context.Context, msg *nlmsg.Message) *syserr.Er // Netstack interfaces are always up. } + return s.setLink(tcpip.NICID(ifinfomsg.Index), attrs) +} + +func (s *Stack) setLink(id tcpip.NICID, linkAttrs map[uint16]nlmsg.BytesView) *syserr.Error { + if v, ok := linkAttrs[linux.IFLA_MASTER]; ok { + master, ok := v.Uint32() + if !ok { + return syserr.ErrInvalidArgument + } + if master != 0 { + if err := s.Stack.SetNICCoordinator(id, tcpip.NICID(master)); err != nil { + return syserr.TranslateNetstackError(err) + } + } + } return nil } @@ -232,6 +247,10 @@ func (s *Stack) newVeth(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesVie return syserr.TranslateNetstackError(err) } ep.SetStack(s.Stack, id) + if err := s.setLink(id, linkAttrs); err != nil { + peerEP.Close() + return err + } if peerName == "" { peerName = fmt.Sprintf("veth%d", peerID) @@ -244,6 +263,34 @@ func (s *Stack) newVeth(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesVie return syserr.TranslateNetstackError(err) } peerEP.SetStack(peerStack.Stack, id) + if peerLinkAttrs != nil { + if err := s.setLink(peerID, peerLinkAttrs); err != nil { + peerStack.Stack.RemoveNIC(peerID) + peerEP.Close() + return err + } + } + + return nil +} + +func (s *Stack) newBridge(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesView, linkInfoAttrs map[uint16]nlmsg.BytesView) *syserr.Error { + ifname := "" + + if v, ok := linkAttrs[linux.IFLA_IFNAME]; ok { + ifname = v.String() + } + ep := stack.NewBridgeEndpoint(defaultMTU) + id := tcpip.NICID(s.Stack.UniqueID()) + err := s.Stack.CreateNICWithOptions(id, ep, stack.NICOptions{ + Name: ifname, + }) + if err != nil { + return syserr.TranslateNetstackError(err) + } + if err := s.setLink(id, linkAttrs); err != nil { + return err + } return nil } @@ -275,6 +322,8 @@ func (s *Stack) newInterface(ctx context.Context, msg *nlmsg.Message, linkAttrs switch kind { case "": return syserr.ErrInvalidArgument + case "bridge": + return s.newBridge(ctx, linkAttrs, linkInfoAttrs) case "veth": return s.newVeth(ctx, linkAttrs, linkInfoAttrs) } diff --git a/pkg/tcpip/stack/BUILD b/pkg/tcpip/stack/BUILD index 0ed67d27e..dcc284e52 100644 --- a/pkg/tcpip/stack/BUILD +++ b/pkg/tcpip/stack/BUILD @@ -28,6 +28,13 @@ declare_rwmutex( prefix = "route", ) +declare_rwmutex( + name = "bridge_mutex", + out = "bridge_mutex.go", + package = "stack", + prefix = "bridge", +) + declare_rwmutex( name = "route_stack_mutex", out = "route_stack_mutex.go", @@ -201,6 +208,8 @@ go_library( "address_state_refs.go", "addressable_endpoint_state.go", "addressable_endpoint_state_mutex.go", + "bridge.go", + "bridge_mutex.go", "bucket_mutex.go", "cleanup_endpoints_mutex.go", "conn_mutex.go", @@ -336,3 +345,21 @@ go_test( "@com_github_google_go_cmp//cmp/cmpopts:go_default_library", ], ) + +go_test( + name = "bridge_test", + size = "small", + srcs = [ + "bridge_test.go", + ], + deps = [ + "//pkg/buffer", + "//pkg/refs", + "//pkg/tcpip", + "//pkg/tcpip/header", + "//pkg/tcpip/link/channel", + "//pkg/tcpip/link/ethernet", + "//pkg/tcpip/link/veth", + "//pkg/tcpip/stack", + ], +) diff --git a/pkg/tcpip/stack/bridge.go b/pkg/tcpip/stack/bridge.go new file mode 100644 index 000000000..5e1f950ee --- /dev/null +++ b/pkg/tcpip/stack/bridge.go @@ -0,0 +1,226 @@ +// Copyright 2024 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stack + +import ( + "math/rand" + "net" + + "gvisor.dev/gvisor/pkg/atomicbitops" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/header" +) + +var _ NetworkLinkEndpoint = (*BridgeEndpoint)(nil) + +type bridgePort struct { + bridge *BridgeEndpoint + nic *nic +} + +// ParseHeader implements stack.LinkEndpoint. +func (p *bridgePort) ParseHeader(pkt *PacketBuffer) bool { + _, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize) + return ok +} + +// DeliverNetworkPacket implements stack.NetworkDispatcher. +func (p *bridgePort) DeliverNetworkPacket(protocol tcpip.NetworkProtocolNumber, pkt *PacketBuffer) { + bridge := p.bridge + bridge.mu.Lock() + defer bridge.mu.Unlock() + + // Send the packet to all other ports. + for _, port := range bridge.ports { + if p == port { + continue + } + newPkt := NewPacketBuffer(PacketBufferOptions{ + ReserveHeaderBytes: int(port.nic.MaxHeaderLength()), + Payload: pkt.ToBuffer(), + }) + port.nic.writeRawPacket(newPkt) + newPkt.DecRef() + } + + bridge.injectInboundLocked(protocol, pkt) +} + +func (p *bridgePort) DeliverLinkPacket(protocol tcpip.NetworkProtocolNumber, pkt *PacketBuffer) { +} + +func getRandMacAddr() tcpip.LinkAddress { + mac := make(net.HardwareAddr, 6) + rand.Read(mac) // Fill with random data. + mac[0] &^= 0x1 // Clear multicast bit. + mac[0] |= 0x2 // Set local assignment bit (IEEE802). + return tcpip.LinkAddress(mac) +} + +// NewBridgeEndpoint creates a new bridge endpoint. +func NewBridgeEndpoint(mtu uint32) *BridgeEndpoint { + b := &BridgeEndpoint{ + mtu: mtu, + addr: getRandMacAddr(), + } + b.ports = make(map[tcpip.NICID]*bridgePort) + return b +} + +// BridgeEndpoint is a bridge endpoint. +type BridgeEndpoint struct { + mu bridgeRWMutex + // +checklocks:mu + ports map[tcpip.NICID]*bridgePort + // +checklocks:mu + dispatcher NetworkDispatcher + // +checklocks:mu + addr tcpip.LinkAddress + // +checklocks:mu + attached bool + mtu uint32 + maxHeaderLength atomicbitops.Uint32 +} + +// WritePackets implements stack.LinkEndpoint.WritePackets. +func (b *BridgeEndpoint) WritePackets(pkts PacketBufferList) (int, tcpip.Error) { + b.mu.RLock() + defer b.mu.RUnlock() + + pktsSlice := pkts.AsSlice() + n := len(pktsSlice) + for _, p := range b.ports { + for _, pkt := range pktsSlice { + // In order to properly loop back to the inbound side we must create a + // fresh packet that only contains the underlying payload with no headers + // or struct fields set. + newPkt := NewPacketBuffer(PacketBufferOptions{ + Payload: pkt.ToBuffer(), + ReserveHeaderBytes: int(p.nic.MaxHeaderLength()), + }) + newPkt.EgressRoute = pkt.EgressRoute + newPkt.NetworkProtocolNumber = pkt.NetworkProtocolNumber + p.nic.writePacket(newPkt) + newPkt.DecRef() + } + } + + return n, nil +} + +// AddNIC adds the specified NIC to the bridge. +func (b *BridgeEndpoint) AddNIC(n *nic) tcpip.Error { + b.mu.Lock() + defer b.mu.Unlock() + + port := &bridgePort{ + nic: n, + bridge: b, + } + n.NetworkLinkEndpoint.Attach(port) + b.ports[n.id] = port + + if b.maxHeaderLength.Load() < uint32(n.MaxHeaderLength()) { + b.maxHeaderLength.Store(uint32(n.MaxHeaderLength())) + } + + return nil +} + +// DelNIC remove the specified NIC from the bridge. +func (b *BridgeEndpoint) DelNIC(nic *nic) tcpip.Error { + b.mu.Lock() + defer b.mu.Unlock() + + delete(b.ports, nic.id) + nic.NetworkLinkEndpoint.Attach(nic) + return nil +} + +// +checklocks:b.mu +func (b *BridgeEndpoint) injectInboundLocked(protocol tcpip.NetworkProtocolNumber, pkt *PacketBuffer) { + d := b.dispatcher + if d != nil { + d.DeliverNetworkPacket(protocol, pkt) + } +} + +// MTU implements stack.LinkEndpoint.MTU. +func (b *BridgeEndpoint) MTU() uint32 { + if b.mtu > header.EthernetMinimumSize { + return b.mtu - header.EthernetMinimumSize + } + return 0 +} + +// MaxHeaderLength implements stack.LinkEndpoint. +func (b *BridgeEndpoint) MaxHeaderLength() uint16 { + return uint16(b.maxHeaderLength.Load()) +} + +// LinkAddress implements stack.LinkEndpoint.LinkAddress. +func (b *BridgeEndpoint) LinkAddress() tcpip.LinkAddress { + b.mu.Lock() + defer b.mu.Unlock() + return b.addr +} + +// SetLinkAddress implements stack.LinkEndpoint.SetLinkAddress. +func (b *BridgeEndpoint) SetLinkAddress(addr tcpip.LinkAddress) { + b.mu.Lock() + defer b.mu.Unlock() + b.addr = addr +} + +// Capabilities implements stack.LinkEndpoint.Capabilities. +func (b *BridgeEndpoint) Capabilities() LinkEndpointCapabilities { + return CapabilityRXChecksumOffload | CapabilitySaveRestore | CapabilityResolutionRequired +} + +// Attach implements stack.LinkEndpoint.Attach. +func (b *BridgeEndpoint) Attach(dispatcher NetworkDispatcher) { + b.mu.Lock() + defer b.mu.Unlock() + for _, p := range b.ports { + p.nic.Primary = nil + } + b.dispatcher = dispatcher + b.ports = make(map[tcpip.NICID]*bridgePort) +} + +// IsAttached implements stack.LinkEndpoint.IsAttached. +func (b *BridgeEndpoint) IsAttached() bool { + b.mu.RLock() + defer b.mu.RUnlock() + return b.dispatcher != nil +} + +// Wait implements stack.LinkEndpoint.Wait. +func (b *BridgeEndpoint) Wait() { +} + +// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType. +func (b *BridgeEndpoint) ARPHardwareType() header.ARPHardwareType { + return header.ARPHardwareEther +} + +// AddHeader implements stack.LinkEndpoint.AddHeader. +func (b *BridgeEndpoint) AddHeader(pkt *PacketBuffer) { +} + +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (b *BridgeEndpoint) ParseHeader(*PacketBuffer) bool { + return true +} diff --git a/pkg/tcpip/stack/bridge_test.go b/pkg/tcpip/stack/bridge_test.go new file mode 100644 index 000000000..4b9511ed7 --- /dev/null +++ b/pkg/tcpip/stack/bridge_test.go @@ -0,0 +1,160 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bridge_test + +import ( + "os" + "testing" + + "gvisor.dev/gvisor/pkg/buffer" + "gvisor.dev/gvisor/pkg/refs" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/link/channel" + "gvisor.dev/gvisor/pkg/tcpip/link/ethernet" + "gvisor.dev/gvisor/pkg/tcpip/link/veth" + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +func TestWritePacketFromBridge(t *testing.T) { + const ( + localLinkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06") + remoteLinkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x07") + bridgeLinkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x08") + + netProto = 55 + nicID = 5 + bridgeID = 6 + ) + + c := channel.New(1, header.EthernetMinimumSize, localLinkAddr) + + s := stack.New(stack.Options{}) + if err := s.CreateNIC(nicID, ethernet.New(c)); err != nil { + t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err) + } + bridgeEndpoint := stack.NewBridgeEndpoint(1500) + bridgeEndpoint.SetLinkAddress(bridgeLinkAddr) + if err := s.CreateNIC(bridgeID, bridgeEndpoint); err != nil { + t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err) + } + if err := s.SetNICCoordinator(nicID, bridgeID); err != nil { + t.Fatalf("s.SetNICCoordinator") + } + + if err := s.WritePacketToRemote(bridgeID, remoteLinkAddr, netProto, buffer.Buffer{}); err != nil { + t.Fatalf("s.WritePacketToRemote(%d, %s, _): %s", bridgeID, remoteLinkAddr, err) + } + pkt := c.Read() + if pkt == nil { + t.Fatal("expected to read a packet") + } + + eth := header.Ethernet(pkt.LinkHeader().Slice()) + pkt.DecRef() + if got := eth.SourceAddress(); got != bridgeLinkAddr { + t.Errorf("got eth.SourceAddress() = %s, want = %s", got, bridgeLinkAddr) + } + if got := eth.DestinationAddress(); got != remoteLinkAddr { + t.Errorf("got eth.DestinationAddress() = %s, want = %s", got, remoteLinkAddr) + } + if got := eth.Type(); got != netProto { + t.Errorf("got eth.Type() = %d, want = %d", got, netProto) + } +} + +type testNotification struct { + ch chan bool +} + +func (n *testNotification) WriteNotify() { + n.ch <- true +} + +func TestWritePacketBetweenDevices(t *testing.T) { + const ( + localLinkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06") + remoteLinkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x07") + bridgeLinkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x08") + + netProto = 55 + nicID = 5 + vethID = 7 + bridgeID = 6 + ) + + veth1, veth2 := veth.NewPair(1500) + secondStack := stack.New(stack.Options{}) + if err := secondStack.CreateNIC(vethID, ethernet.New(veth2)); err != nil { + t.Fatalf("s.CreateNIC(%d, _): %s", vethID, err) + } + veth2.SetStack(secondStack, vethID) + veth2.SetLinkAddress(localLinkAddr) + + s := stack.New(stack.Options{}) + bridgeEndpoint := stack.NewBridgeEndpoint(1500) + bridgeEndpoint.SetLinkAddress(bridgeLinkAddr) + if err := s.CreateNIC(bridgeID, bridgeEndpoint); err != nil { + t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err) + } + + c := channel.New(1, header.EthernetMinimumSize, localLinkAddr) + c.SetLinkAddress(remoteLinkAddr) + if err := s.CreateNIC(nicID, ethernet.New(c)); err != nil { + t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err) + } + if err := s.SetNICCoordinator(nicID, bridgeID); err != nil { + t.Fatalf("s.SetNICCoordinator") + } + + if err := s.CreateNIC(vethID, ethernet.New(veth1)); err != nil { + t.Fatalf("s.CreateNIC(%d, _): %s", vethID, err) + } + veth1.SetStack(s, vethID) + if err := s.SetNICCoordinator(vethID, bridgeID); err != nil { + t.Fatalf("s.SetNICCoordinator") + } + + n := &testNotification{ch: make(chan bool, 1)} + c.AddNotify(n) + if err := secondStack.WritePacketToRemote(vethID, remoteLinkAddr, netProto, buffer.Buffer{}); err != nil { + t.Fatalf("s.WritePacketToRemote(%d, %s, _): %s", bridgeID, remoteLinkAddr, err) + } + <-n.ch + pkt := c.Read() + if pkt == nil { + t.Fatal("expected to read a packet") + } + + pkt.LinkHeader().Consume(header.EthernetMinimumSize) + eth := header.Ethernet(pkt.LinkHeader().Slice()) + pkt.DecRef() + if got := eth.SourceAddress(); got != localLinkAddr { + t.Errorf("got eth.SourceAddress() = %s, want = %s", got, localLinkAddr) + } + if got := eth.DestinationAddress(); got != remoteLinkAddr { + t.Errorf("got eth.DestinationAddress() = %s, want = %s", got, remoteLinkAddr) + } + if got := eth.Type(); got != netProto { + t.Errorf("got eth.Type() = %d, want = %d", got, netProto) + } +} + +func TestMain(m *testing.M) { + refs.SetLeakMode(refs.LeaksPanic) + code := m.Run() + refs.DoLeakCheck() + os.Exit(code) +} diff --git a/pkg/tcpip/stack/nic.go b/pkg/tcpip/stack/nic.go index 8d7b7b76e..e7979122e 100644 --- a/pkg/tcpip/stack/nic.go +++ b/pkg/tcpip/stack/nic.go @@ -87,6 +87,9 @@ type nic struct { // deliverLinkPackets is off by default because some users already // deliver link packets by explicitly calling nic.DeliverLinkPackets. deliverLinkPackets bool + + // Primary is the main controlling interface in a bonded setup. + Primary *nic } // makeNICStats initializes the NIC statistics and associates them to the global @@ -1079,3 +1082,11 @@ func (n *nic) multicastForwarding(protocol tcpip.NetworkProtocolNumber) (bool, t return ep.MulticastForwarding(), nil } + +// CoordinatorNIC represents NetworkLinkEndpoint that can join multiple network devices. +type CoordinatorNIC interface { + // AddNIC adds the specified NIC device. + AddNIC(n *nic) tcpip.Error + // DelNIC deletes the specified NIC device. + DelNIC(n *nic) tcpip.Error +} diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 938a122ac..824a691cd 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -843,6 +843,18 @@ type NICOptions struct { DeliverLinkPackets bool } +// GetNICByID return a network device associated with the specified ID. +func (s *Stack) GetNICByID(id tcpip.NICID) (*nic, tcpip.Error) { + s.mu.Lock() + defer s.mu.Unlock() + + n, ok := s.nics[id] + if !ok { + return nil, &tcpip.ErrNoSuchFile{} + } + return n, nil +} + // CreateNICWithOptions creates a NIC with the provided id, LinkEndpoint, and // NICOptions. See the documentation on type NICOptions for details on how // NICs can be configured. @@ -964,6 +976,13 @@ func (s *Stack) removeNICLocked(id tcpip.NICID) tcpip.Error { } delete(s.nics, id) + if nic.Primary != nil { + b := nic.Primary.NetworkLinkEndpoint.(CoordinatorNIC) + if err := b.DelNIC(nic); err != nil { + return err + } + } + // Remove routes in-place. n tracks the number of routes written. s.routeMu.Lock() n := 0 @@ -981,6 +1000,31 @@ func (s *Stack) removeNICLocked(id tcpip.NICID) tcpip.Error { return nic.remove() } +// SetNICCoordinator sets a coordinator device. +func (s *Stack) SetNICCoordinator(id tcpip.NICID, mid tcpip.NICID) tcpip.Error { + s.mu.Lock() + defer s.mu.Unlock() + + nic, ok := s.nics[id] + if !ok { + return &tcpip.ErrUnknownNICID{} + } + + m, ok := s.nics[mid] + if !ok { + return &tcpip.ErrUnknownNICID{} + } + b, ok := m.NetworkLinkEndpoint.(CoordinatorNIC) + if !ok { + return &tcpip.ErrNotSupported{} + } + if err := b.AddNIC(nic); err != nil { + return err + } + nic.Primary = m + return nil +} + // NICInfo captures the name and addresses assigned to a NIC. type NICInfo struct { Name string