netstack: implement bridge devices

Implement the core part. All packets are broadcast-ed to all ports. The next
step will be to implement forwarding and multicast group databases.

PiperOrigin-RevId: 644471256
This commit is contained in:
Andrei Vagin
2024-06-18 12:15:19 -07:00
committed by gVisor bot
parent 4b965591e9
commit 0d20b827d2
6 changed files with 517 additions and 0 deletions
+49
View File
@@ -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)
}
+27
View File
@@ -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",
],
)
+226
View File
@@ -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
}
+160
View File
@@ -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)
}
+11
View File
@@ -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
}
+44
View File
@@ -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