blob: 79aed3faed9abd7c5d5c4e23c5fd966ad7c79185 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//go:build !build_with_native_toolchain
// +build !build_with_native_toolchain
package netdevice
import (
"context"
"fmt"
"math"
"math/bits"
"sync"
"syscall/zx"
"syscall/zx/fidl"
"gen/netstack/link/netdevice"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link/fifo"
syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go"
"fidl/fuchsia/hardware/network"
"go.uber.org/multierr"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// #include <zircon/device/network.h>
import "C"
const descriptorLength uint64 = C.sizeof_buffer_descriptor_t
const tag = "netdevice"
const emptyLinkAddress tcpip.LinkAddress = ""
// TODO(https://fxbug.dev/64310): Remove port 0 assumptions once netstack FIDL
// supports ports.
const port0 = 0
type bufferDescriptor = C.buffer_descriptor_t
var _ link.Controller = (*Client)(nil)
var _ link.Observer = (*Client)(nil)
var _ stack.LinkEndpoint = (*Client)(nil)
var _ stack.GSOEndpoint = (*Client)(nil)
// InfoProvider abstracts a common information interface for different Clients.
type InfoProvider interface {
RxStats() *fifo.RxStats
TxStats() *fifo.TxStats
Class() network.DeviceClass
}
var _ InfoProvider = (*Client)(nil)
// A client for a network device that implements the
// fuchsia.hardware.network.Device protocol.
type Client struct {
dispatcher stack.NetworkDispatcher
// WaitGroup on all the go routines associated with the Client. See Attach
// and Wait.
dispatcherWg sync.WaitGroup
device *network.DeviceWithCtxInterface
port *network.PortWithCtxInterface
session *network.SessionWithCtxInterface
deviceInfo network.DeviceInfo
portInfo network.PortInfo
config SessionConfig
watcher *network.StatusWatcherWithCtxInterface
data fifo.MappedVMO
descriptors fifo.MappedVMO
handler netdevice.Handler
state struct {
mu struct {
sync.Mutex
closed bool
onLinkClosed func()
onLinkOnlineChanged func(bool)
}
}
mtu struct {
mu struct {
sync.Mutex
value uint32
}
}
}
func (c *Client) MTU() uint32 {
c.mtu.mu.Lock()
mtu := c.mtu.mu.value
c.mtu.mu.Unlock()
return mtu
}
func (*Client) Capabilities() stack.LinkEndpointCapabilities {
return 0
}
func (c *Client) MaxHeaderLength() uint16 {
return 0
}
func (c *Client) LinkAddress() tcpip.LinkAddress {
// NOTE: Plain Client does not have a link address. Only MacAddressingClient
// does.
return emptyLinkAddress
}
// write writes a list of packets to the device.
func (c *Client) write(pkts stack.PacketBufferList, protocol tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
return c.handler.ProcessWrite(pkts, func(descriptorIndex *uint16, pkt *stack.PacketBuffer) {
descriptor := c.getDescriptor(*descriptorIndex)
// Reset descriptor to default values before filling it.
c.resetTxDescriptor(descriptor)
data := c.getDescriptorData(descriptor)
n := 0
for _, v := range pkt.Views() {
if w := copy(data[n:], v); w != len(v) {
panic(fmt.Sprintf("failed to copy packet data to descriptor %d, want %d got %d bytes", descriptorIndex, len(v), w))
} else {
n += w
}
}
var frameType network.FrameType
if len(pkt.LinkHeader().View()) != 0 {
frameType = network.FrameTypeEthernet
} else {
switch protocol {
case header.IPv4ProtocolNumber:
frameType = network.FrameTypeIpv4
case header.IPv6ProtocolNumber:
frameType = network.FrameTypeIpv6
default:
_ = syslog.ErrorTf(tag, "can't identify outgoing packet type")
}
}
// Pad tx frame to device requirements.
for ; n < int(c.deviceInfo.MinTxBufferLength); n++ {
data[n] = 0
}
descriptor.info_type = C.uint(network.InfoTypeNoInfo)
descriptor.frame_type = C.uchar(frameType)
descriptor.data_length = C.uint(n)
})
}
func (c *Client) WritePacket(_ stack.RouteInfo, proto tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
var pkts stack.PacketBufferList
pkts.PushBack(pkt)
_, err := c.write(pkts, proto)
return err
}
func (c *Client) WritePackets(_ stack.RouteInfo, pkts stack.PacketBufferList, proto tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
return c.write(pkts, proto)
}
func (*Client) WriteRawPacket(*stack.PacketBuffer) tcpip.Error { return &tcpip.ErrNotSupported{} }
func (c *Client) Attach(dispatcher stack.NetworkDispatcher) {
c.dispatcher = dispatcher
detachWithError := func(reason error) {
c.state.mu.Lock()
closed := c.state.mu.closed
c.state.mu.Unlock()
if closed {
return
}
c.handler.DetachTx()
if err := c.Close(); err != nil {
_ = syslog.WarnTf(tag, "error closing device on detach (%s): %s", reason, err)
} else {
_ = syslog.WarnTf(tag, "closed device: %s", reason)
}
}
// dispatcher may be nil when the NIC in stack.Stack is being removed.
if dispatcher == nil {
detachWithError(fmt.Errorf("RemoveNIC"))
return
}
c.dispatcherWg.Add(1)
go func() {
defer c.dispatcherWg.Done()
for {
status, err := c.watcher.WatchStatus(context.Background())
if err != nil {
detachWithError(fmt.Errorf("watcher loop: %w", err))
return
}
if status.HasMtu() {
c.mtu.mu.Lock()
c.mtu.mu.value = status.GetMtu()
c.mtu.mu.Unlock()
}
c.state.mu.Lock()
fn := c.state.mu.onLinkOnlineChanged
c.state.mu.Unlock()
fn(status.HasFlags() && status.GetFlags()&network.StatusFlagsOnline != 0)
}
}()
c.dispatcherWg.Add(1)
go func() {
defer c.dispatcherWg.Done()
if err := c.handler.TxReceiverLoop(func(descriptorIndex *uint16) bool {
descriptor := c.getDescriptor(*descriptorIndex)
return network.TxReturnFlags(descriptor.return_flags)&network.TxReturnFlagsTxRetError == 0
}); err != nil {
detachWithError(fmt.Errorf("TX read loop: %w", err))
}
_ = syslog.VLogTf(syslog.DebugVerbosity, tag, "TX read loop finished")
}()
c.dispatcherWg.Add(1)
go func() {
defer c.dispatcherWg.Done()
if err := c.handler.TxSenderLoop(); err != nil {
detachWithError(fmt.Errorf("TX write loop: %w", err))
}
_ = syslog.VLogTf(syslog.DebugVerbosity, tag, "TX write loop finished")
}()
c.dispatcherWg.Add(1)
go func() {
defer c.dispatcherWg.Done()
if err := c.handler.RxLoop(func(descriptorIndex *uint16) {
descriptor := c.getDescriptor(*descriptorIndex)
data := c.getDescriptorData(descriptor)
view := buffer.NewView(len(data))
view = view[:copy(view, data)]
var protocolNumber tcpip.NetworkProtocolNumber
switch network.FrameType(descriptor.frame_type) {
case network.FrameTypeIpv4:
protocolNumber = header.IPv4ProtocolNumber
case network.FrameTypeIpv6:
protocolNumber = header.IPv6ProtocolNumber
}
dispatcher.DeliverNetworkPacket(emptyLinkAddress, emptyLinkAddress, protocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: view.ToVectorisedView(),
}))
// This entry is going back to the driver; it can be reused.
c.resetRxDescriptor(descriptor)
}); err != nil {
detachWithError(fmt.Errorf("RX loop: %w", err))
}
_ = syslog.VLogTf(syslog.DebugVerbosity, tag, "Rx loop finished")
}()
// Spawn a goroutine to clean up the mapped memory once all the handler
// loops are done.
go func() {
c.dispatcherWg.Wait()
if err := multierr.Combine(c.data.Close(), c.descriptors.Close()); err != nil {
_ = syslog.WarnTf(tag, "failed to close mapped VMOs: %s", err)
}
c.state.mu.Lock()
fn := c.state.mu.onLinkClosed
c.state.mu.Unlock()
fn()
}()
}
func (c *Client) IsAttached() bool {
return c.dispatcher != nil
}
func (c *Client) Wait() {
c.dispatcherWg.Wait()
}
func (*Client) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
func (*Client) AddHeader(_, _ tcpip.LinkAddress, _ tcpip.NetworkProtocolNumber, _ *stack.PacketBuffer) {
}
// GSOMaxSize implements stack.GSOEndpoint.
func (*Client) GSOMaxSize() uint32 {
// There's no limit on how much data we can take in a single software GSO write.
return math.MaxUint32
}
// SupportedGSO implements stack.GSOEndpoint.
func (*Client) SupportedGSO() stack.SupportedGSO {
// TODO(https://fxbug.dev/76010): expose hardware offloading capabilities.
return stack.SWGSOSupported
}
func (c *Client) Up() error {
result, err := c.session.Attach(context.Background(), port0, c.config.RxFrames)
if err != nil {
return err
}
switch w := result.Which(); w {
case network.SessionAttachResultResponse:
return nil
case network.SessionAttachResultErr:
return &zx.Error{
Status: zx.Status(result.Err),
Text: "failed to attach session",
}
default:
panic(fmt.Sprintf("unexpected session.Attach variant %d", w))
}
}
func (c *Client) Down() error {
result, err := c.session.Detach(context.Background(), port0)
if err != nil {
return err
}
switch w := result.Which(); w {
case network.SessionDetachResultResponse:
return nil
case network.SessionDetachResultErr:
return &zx.Error{
Status: zx.Status(result.Err),
Text: "failed to detach session",
}
default:
panic(fmt.Sprintf("unexpected session.Detach variant %d", w))
}
}
func (c *Client) SetOnLinkClosed(f func()) {
c.state.mu.Lock()
c.state.mu.onLinkClosed = f
c.state.mu.Unlock()
}
func (c *Client) SetOnLinkOnlineChanged(f func(bool)) {
c.state.mu.Lock()
c.state.mu.onLinkOnlineChanged = f
c.state.mu.Unlock()
}
func (c *Client) SetPromiscuousMode(bool) error {
return fmt.Errorf("promiscuous mode not supported on device")
}
func (c *Client) DeviceClass() network.DeviceClass {
return c.portInfo.Class
}
// Closes the client and disposes of all its resources.
func (c *Client) Close() error {
c.state.mu.Lock()
defer c.state.mu.Unlock()
if c.state.mu.closed {
return nil
}
c.state.mu.closed = true
c.handler.DetachTx()
return multierr.Combine(
c.device.Close(),
// Session also has a Close method, make sure we're calling the ChannelProxy
// one.
((*fidl.ChannelProxy)(c.session)).Close(),
c.handler.RxFifo.Close(),
c.handler.TxFifo.Close(),
c.watcher.Close(),
// Additional cleanup is performed by the watcher goroutine spawned in
// Attach once all the io loops are done.
)
}
// getDescriptor returns the shared memory representing the descriptor indexed
// by d.
func (c *Client) getDescriptor(d uint16) *bufferDescriptor {
offset := uint64(d) * c.config.DescriptorLength
return (*bufferDescriptor)(c.descriptors.GetPointer(offset))
}
// getDescriptorData returns the shared contiguous memory for a descriptor.
func (c *Client) getDescriptorData(desc *bufferDescriptor) []byte {
if desc.chain_length != 0 {
panic(fmt.Sprintf("descriptor chaining not implemented, descriptor requested chain of length %d", desc.chain_length))
}
offset := uint64(desc.offset) + uint64(desc.head_length)
return c.data.GetData(offset, uint64(desc.data_length))
}
// resetTxDescriptor resets the the descriptor's fields that a device
// implementation could have changed. It should only be used for
// Tx buffers.
func (c *Client) resetTxDescriptor(descriptor *bufferDescriptor) {
*descriptor = bufferDescriptor{
info_type: C.uint(network.InfoTypeNoInfo),
offset: descriptor.offset,
head_length: C.ushort(c.config.TxHeaderLength),
// Note: we assert that BufferLength > TxHeaderLength + TxTailLength when
// the session config is created, so we don't have to worry about overflow
// here.
data_length: C.uint(c.config.BufferLength - uint32(c.config.TxHeaderLength) - uint32(c.config.TxTailLength)),
tail_length: C.ushort(c.config.TxTailLength),
}
}
// resetRxDescriptor resets the the descriptor's fields that a device
// implementation could have changed. It should only be used for
// Rx buffers.
func (c *Client) resetRxDescriptor(descriptor *bufferDescriptor) {
*descriptor = bufferDescriptor{
info_type: C.uint(network.InfoTypeNoInfo),
offset: descriptor.offset,
data_length: C.uint(c.config.BufferLength),
}
}
// NewClient creates a new client from a provided network device interface.
func NewClient(ctx context.Context, dev *network.DeviceWithCtxInterface, sessionConfigFactory SessionConfigFactory) (*Client, error) {
_ = syslog.VLogTf(syslog.DebugVerbosity, tag, "creating network device client")
deviceInfo, err := dev.GetInfo(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get device information: %w", err)
}
if !(deviceInfo.HasMinDescriptorLength() &&
deviceInfo.HasDescriptorVersion() &&
deviceInfo.HasRxDepth() &&
deviceInfo.HasTxDepth() &&
deviceInfo.HasBufferAlignment() &&
deviceInfo.HasMaxBufferLength() &&
deviceInfo.HasMinRxBufferLength() &&
deviceInfo.HasMinTxBufferLength() &&
deviceInfo.HasMinTxBufferHead() &&
deviceInfo.HasMinTxBufferTail() &&
deviceInfo.HasMaxBufferParts()) {
return nil, fmt.Errorf("incomplete DeviceInfo: %#v", deviceInfo)
}
portRequest, port, err := network.NewPortWithCtxInterfaceRequest()
if err != nil {
return nil, fmt.Errorf("failed to create port request: %w", err)
}
if err := dev.GetPort(ctx, port0, portRequest); err != nil {
return nil, fmt.Errorf("failed to get port %d: %w", port0, err)
}
portInfo, err := port.GetInfo(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get port info: %w", err)
}
if !(portInfo.HasId() && portInfo.HasClass() && portInfo.HasRxTypes() && portInfo.HasTxTypes()) {
return nil, fmt.Errorf("incomplete PortInfo: %#v", portInfo)
}
portStatus, err := port.GetStatus(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create get port status: %w", err)
}
config, err := sessionConfigFactory.MakeSessionConfig(deviceInfo, portStatus)
if err != nil {
return nil, fmt.Errorf("session configuration factory failed: %w", err)
}
if len(config.RxFrames) == 0 {
config.RxFrames = portInfo.RxTypes
} else {
// Verify that requested frame types are valid.
isValidRxFrame := func(frameType network.FrameType) bool {
for _, f := range portInfo.RxTypes {
if f == frameType {
return true
}
}
return false
}
for _, requested := range config.RxFrames {
if !isValidRxFrame(requested) {
// NB: Rx types is formatted with %v below so it's printed as a slice of
// frame types correctly. %s compiles correctly but is interpreted as
// []uint8, which is itself printed as a string backed by that slice
// when using %s.
return nil, fmt.Errorf("requested unsupported frame type: %s. (should be in %v)", requested, portInfo.RxTypes)
}
}
}
// Descriptor count must be a power of 2.
config.TxDescriptorCount = 1 << bits.Len16(config.TxDescriptorCount-1)
config.RxDescriptorCount = 1 << bits.Len16(config.RxDescriptorCount-1)
totalDescriptors := uint64(config.TxDescriptorCount + config.RxDescriptorCount)
mappedDataVmo, dataVmo, err := fifo.NewMappedVMO(totalDescriptors*uint64(config.BufferStride), "fuchsia.hardware.network.Device/descriptors")
if err != nil {
return nil, fmt.Errorf("failed to create data VMO: %w", err)
}
mappedDescVmo, descVmo, err := fifo.NewMappedVMO(totalDescriptors*config.DescriptorLength, "fuchsia.hardware.network.Device/data")
if err != nil {
_ = mappedDataVmo.Close()
return nil, fmt.Errorf("failed to create descriptors VMO: %w", err)
}
var sessionInfo network.SessionInfo
sessionInfo.SetDescriptors(descVmo)
sessionInfo.SetData(dataVmo)
sessionInfo.SetDescriptorVersion(C.NETWORK_DEVICE_DESCRIPTOR_VERSION)
sessionInfo.SetDescriptorLength(uint8(config.DescriptorLength / 8))
sessionInfo.SetDescriptorCount(config.RxDescriptorCount + config.TxDescriptorCount)
sessionInfo.SetOptions(config.Options)
sessionResult, err := dev.OpenSession(ctx, "netstack", sessionInfo)
if err != nil {
_ = mappedDataVmo.Close()
_ = mappedDescVmo.Close()
return nil, fmt.Errorf("failed to open device session: %w", err)
}
if sessionResult.Which() == network.DeviceOpenSessionResultErr {
_ = mappedDataVmo.Close()
_ = mappedDescVmo.Close()
return nil, &zx.Error{Status: zx.Status(sessionResult.Err), Text: "fuchsia.hardware.network/Device.OpenSession"}
}
req, watcher, err := network.NewStatusWatcherWithCtxInterfaceRequest()
if err != nil {
_ = mappedDataVmo.Close()
_ = mappedDescVmo.Close()
return nil, fmt.Errorf("failed to create status watcher request: %w", err)
}
if err := port.GetStatusWatcher(ctx, req, network.MaxStatusBuffer); err != nil {
_ = mappedDataVmo.Close()
_ = mappedDescVmo.Close()
_ = watcher.Close()
return nil, fmt.Errorf("failed to create get status watcher: %w", err)
}
c := &Client{
device: dev,
port: port,
session: &sessionResult.Response.Session,
deviceInfo: deviceInfo,
portInfo: portInfo,
config: config,
watcher: watcher,
data: mappedDataVmo,
descriptors: mappedDescVmo,
handler: netdevice.Handler{
TxDepth: uint32(deviceInfo.TxDepth),
RxDepth: uint32(deviceInfo.RxDepth),
RxFifo: sessionResult.Response.Fifos.Rx,
TxFifo: sessionResult.Response.Fifos.Tx,
},
}
if entries := c.handler.InitRx(c.config.RxDescriptorCount); entries != c.config.RxDescriptorCount {
panic(fmt.Sprintf("Bad handler rx queue size: %d, expected %d", entries, c.config.RxDescriptorCount))
}
if entries := c.handler.InitTx(c.config.TxDescriptorCount); entries != c.config.TxDescriptorCount {
panic(fmt.Sprintf("Bad handler tx queue size: %d, expected %d", entries, c.config.RxDescriptorCount))
}
c.handler.Stats.Tx.FifoStats = fifo.MakeFifoStats(uint32(c.deviceInfo.TxDepth))
c.handler.Stats.Rx.FifoStats = fifo.MakeFifoStats(uint32(c.deviceInfo.RxDepth))
descriptorIndex := uint16(0)
vmoOffset := uint64(0)
for i := uint16(0); i < c.config.RxDescriptorCount; i++ {
descriptor := c.getDescriptor(descriptorIndex)
*descriptor = bufferDescriptor{
offset: C.ulong(vmoOffset),
}
c.resetRxDescriptor(descriptor)
c.handler.PushInitialRx(descriptorIndex)
vmoOffset += uint64(c.config.BufferStride)
descriptorIndex++
}
for i := uint16(0); i < c.config.TxDescriptorCount; i++ {
descriptor := c.getDescriptor(descriptorIndex)
*descriptor = bufferDescriptor{
offset: C.ulong(vmoOffset),
}
c.resetTxDescriptor(descriptor)
c.handler.PushInitialTx(descriptorIndex)
vmoOffset += uint64(c.config.BufferStride)
descriptorIndex++
}
return c, nil
}
// RxStats implements InfoProvider.
func (c *Client) RxStats() *fifo.RxStats {
return &c.handler.Stats.Rx
}
// TxStats implements InfoProvider.
func (c *Client) TxStats() *fifo.TxStats {
return &c.handler.Stats.Tx
}
// Class implements InfoProvider.
func (c *Client) Class() network.DeviceClass {
return c.portInfo.Class
}