blob: fb24dae97bfe4d792c483fb72e3ba8d6aa9252d2 [file] [log] [blame]
// Copyright 2019 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.
package netstack
import (
"fmt"
"reflect"
"sort"
"strconv"
"syscall/zx"
"syscall/zx/dispatch"
"syscall/zx/fidl"
"app/context"
"netstack/dhcp"
"netstack/link"
"netstack/link/eth"
"syslog"
"fidl/fuchsia/hardware/ethernet"
inspect "fidl/fuchsia/inspect/deprecated"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
// An infallible version of fuchsia.inspect.Inspect with FIDL details omitted.
type inspectInner interface {
ReadData() inspect.Object
ListChildren() []string
GetChild(string) inspectInner
}
const (
statsLabel = "Stats"
socketInfo = "Socket Info"
dhcpInfo = "DHCP Info"
ethInfo = "Ethernet Info"
rxReads = "RxReads"
rxWrites = "RxWrites"
txReads = "TxReads"
txWrites = "TxWrites"
)
// An adapter that implements fuchsia.inspect.InspectWithCtx using the above.
var _ inspect.InspectWithCtx = (*inspectImpl)(nil)
type inspectImpl struct {
inner inspectInner
service *inspect.InspectService
}
func (impl *inspectImpl) ReadData(fidl.Context) (inspect.Object, error) {
return impl.inner.ReadData(), nil
}
func (impl *inspectImpl) ListChildren(fidl.Context) ([]string, error) {
return impl.inner.ListChildren(), nil
}
func (impl *inspectImpl) OpenChild(ctx fidl.Context, childName string, childChannel inspect.InspectWithCtxInterfaceRequest) (bool, error) {
if child := impl.inner.GetChild(childName); child != nil {
svc := (&inspectImpl{
inner: child,
service: impl.service,
}).asService()
return true, svc.AddFn(svc.Stub, childChannel.Channel, ctx)
}
return false, nil
}
func (impl *inspectImpl) asService() *context.Service {
return &context.Service{
Stub: &inspect.InspectWithCtxStub{Impl: impl},
AddFn: func(s fidl.Stub, c zx.Channel, ctx fidl.Context) error {
d, ok := dispatch.GetDispatcher(ctx)
if !ok {
panic("no dispatcher provided for FIDL context")
}
_, err := impl.service.BindingSet.AddToDispatcher(s, c, d, nil)
return err
},
}
}
// Inspect implementations are exposed as directories containing a node called "inspect".
var _ context.Directory = (*inspectDirectory)(nil)
type inspectDirectory struct {
asService func() *context.Service
}
func (dir *inspectDirectory) Get(name string) (context.Node, bool) {
if name == inspect.InspectName {
return dir.asService(), true
}
return nil, false
}
func (dir *inspectDirectory) ForEach(fn func(string, context.Node)) {
fn(inspect.InspectName, dir.asService())
}
// statCounter enables *tcpip.StatCounters and other types in this
// package to be accessed via the same interface.
type statCounter interface {
Value() uint64
}
var _ statCounter = (*tcpip.StatCounter)(nil)
var statCounterType = reflect.TypeOf((*statCounter)(nil)).Elem()
// Recursive reflection-based implementation for structs containing only other
// structs and stat counters.
var _ inspectInner = (*statCounterInspectImpl)(nil)
type statCounterInspectImpl struct {
name string
value reflect.Value
}
func (impl *statCounterInspectImpl) ReadData() inspect.Object {
return inspect.Object{
Name: impl.name,
Metrics: impl.asMetrics(),
}
}
func (impl *statCounterInspectImpl) asMetrics() []inspect.Metric {
var metrics []inspect.Metric
typ := impl.value.Type()
for i := 0; i < impl.value.NumField(); i++ {
// PkgPath is empty for exported field names.
if field := typ.Field(i); len(field.PkgPath) == 0 {
v := impl.value.Field(i)
counter, ok := v.Interface().(statCounter)
if !ok && v.CanAddr() {
counter, ok = v.Addr().Interface().(statCounter)
}
if ok {
metrics = append(metrics, inspect.Metric{
Key: field.Name,
Value: inspect.MetricValueWithUintValue(counter.Value()),
})
} else if field.Anonymous && v.Kind() == reflect.Struct {
metrics = append(metrics, (&statCounterInspectImpl{value: v}).asMetrics()...)
}
}
}
return metrics
}
func (impl *statCounterInspectImpl) ListChildren() []string {
var children []string
typ := impl.value.Type()
for i := 0; i < impl.value.NumField(); i++ {
// PkgPath is empty for exported field names.
//
// Avoid inspecting any field that implements statCounter.
if field := typ.Field(i); len(field.PkgPath) == 0 && field.Type.Kind() == reflect.Struct && !field.Type.Implements(statCounterType) && !reflect.PtrTo(field.Type).Implements(statCounterType) {
if field.Anonymous {
children = append(children, (&statCounterInspectImpl{value: impl.value.Field(i)}).ListChildren()...)
} else {
children = append(children, field.Name)
}
}
}
return children
}
func (impl *statCounterInspectImpl) GetChild(childName string) inspectInner {
if typ, ok := impl.value.Type().FieldByName(childName); ok {
if len(typ.PkgPath) == 0 && typ.Type.Kind() == reflect.Struct {
// PkgPath is empty for exported field names.
if child := impl.value.FieldByName(childName); child.IsValid() {
return &statCounterInspectImpl{
name: childName,
value: child,
}
}
}
}
return nil
}
var _ inspectInner = (*nicInfoMapInspectImpl)(nil)
// Picking info to inspect from ifState in netstack.
type ifStateInfo struct {
stack.NICInfo
nicid tcpip.NICID
state link.State
dnsServers []tcpip.Address
dhcpEnabled bool
dhcpInfo dhcp.Info
dhcpStats *dhcp.Stats
client *eth.Client
}
type nicInfoMapInspectImpl struct {
value map[tcpip.NICID]ifStateInfo
}
func (impl *nicInfoMapInspectImpl) ReadData() inspect.Object {
return inspect.Object{
Name: "NICs",
}
}
func (impl *nicInfoMapInspectImpl) ListChildren() []string {
var children []string
for nicID := range impl.value {
children = append(children, strconv.FormatUint(uint64(nicID), 10))
}
sort.Strings(children)
return children
}
func (impl *nicInfoMapInspectImpl) GetChild(childName string) inspectInner {
id, err := strconv.ParseInt(childName, 10, 32)
if err != nil {
syslog.VLogTf(syslog.DebugVerbosity, inspect.InspectName, "OpenChild: %s", err)
return nil
}
if child, ok := impl.value[tcpip.NICID(id)]; ok {
return &nicInfoInspectImpl{
name: childName,
value: child,
}
}
return nil
}
var _ inspectInner = (*nicInfoInspectImpl)(nil)
type nicInfoInspectImpl struct {
name string
value ifStateInfo
}
func (impl *nicInfoInspectImpl) ReadData() inspect.Object {
object := inspect.Object{
Name: impl.name,
Properties: []inspect.Property{
{Key: "Name", Value: inspect.PropertyValueWithStr(impl.value.Name)},
{Key: "Up", Value: inspect.PropertyValueWithStr(strconv.FormatBool(impl.value.Flags.Up))},
{Key: "Running", Value: inspect.PropertyValueWithStr(strconv.FormatBool(impl.value.Flags.Running))},
{Key: "Loopback", Value: inspect.PropertyValueWithStr(strconv.FormatBool(impl.value.Flags.Loopback))},
{Key: "Promiscuous", Value: inspect.PropertyValueWithStr(strconv.FormatBool(impl.value.Flags.Promiscuous))},
},
Metrics: []inspect.Metric{
{Key: "MTU", Value: inspect.MetricValueWithUintValue(uint64(impl.value.MTU))},
},
}
if linkAddress := impl.value.LinkAddress; len(linkAddress) != 0 {
object.Properties = append(object.Properties, inspect.Property{
Key: "LinkAddress",
Value: inspect.PropertyValueWithStr(linkAddress.String()),
})
}
for i, protocolAddress := range impl.value.ProtocolAddresses {
protocol := "unknown"
switch protocolAddress.Protocol {
case header.IPv4ProtocolNumber:
protocol = "ipv4"
case header.IPv6ProtocolNumber:
protocol = "ipv6"
case header.ARPProtocolNumber:
protocol = "arp"
}
object.Properties = append(object.Properties, inspect.Property{
Key: fmt.Sprintf("ProtocolAddress%d", i),
Value: inspect.PropertyValueWithStr(fmt.Sprintf("[%s] %s", protocol, protocolAddress.AddressWithPrefix)),
})
}
for i, addr := range impl.value.dnsServers {
object.Properties = append(object.Properties, inspect.Property{
Key: fmt.Sprintf("DNS server%d", i), Value: inspect.PropertyValueWithStr(addr.String())})
}
object.Properties = append(object.Properties, inspect.Property{Key: "DHCP enabled", Value: inspect.PropertyValueWithStr(strconv.FormatBool(impl.value.dhcpEnabled))})
return object
}
func (impl *nicInfoInspectImpl) ListChildren() []string {
children := []string{
statsLabel,
}
if impl.value.dhcpEnabled {
children = append(children, dhcpInfo)
}
if impl.value.client != nil {
children = append(children, ethInfo)
}
return children
}
func (impl *nicInfoInspectImpl) GetChild(childName string) inspectInner {
switch childName {
case statsLabel:
return &statCounterInspectImpl{
name: childName,
value: reflect.ValueOf(impl.value.Stats),
}
case dhcpInfo:
return &dhcpInfoInspectImpl{
name: childName,
info: impl.value.dhcpInfo,
stats: impl.value.dhcpStats,
}
case ethInfo:
return &ethInfoInspectImpl{
name: childName,
value: impl.value.client,
}
default:
return nil
}
}
var _ inspectInner = (*dhcpInfoInspectImpl)(nil)
type dhcpInfoInspectImpl struct {
name string
info dhcp.Info
stats *dhcp.Stats
}
func (impl *dhcpInfoInspectImpl) ReadData() inspect.Object {
addrString := func(addr tcpip.Address) string {
if addr == "" {
return "[none]"
}
return addr.String()
}
addrPrefixString := func(addr tcpip.AddressWithPrefix) string {
if addr == (tcpip.AddressWithPrefix{}) {
return "[none]"
}
return addr.String()
}
return inspect.Object{
Name: impl.name,
Properties: []inspect.Property{
{Key: "State", Value: inspect.PropertyValueWithStr(impl.info.State.String())},
{Key: "AcquiredAddress", Value: inspect.PropertyValueWithStr(addrPrefixString(impl.info.Addr))},
{Key: "ServerAddress", Value: inspect.PropertyValueWithStr(addrString(impl.info.Server))},
{Key: "OldAddress", Value: inspect.PropertyValueWithStr(addrPrefixString(impl.info.OldAddr))},
{Key: "Acquisition", Value: inspect.PropertyValueWithStr(impl.info.Acquisition.String())},
{Key: "Backoff", Value: inspect.PropertyValueWithStr(impl.info.Backoff.String())},
{Key: "Retransmission", Value: inspect.PropertyValueWithStr(impl.info.Retransmission.String())},
},
}
}
func (impl *dhcpInfoInspectImpl) ListChildren() []string {
return []string{
statsLabel,
}
}
func (impl *dhcpInfoInspectImpl) GetChild(childName string) inspectInner {
switch childName {
case statsLabel:
return &statCounterInspectImpl{
name: childName,
value: reflect.ValueOf(impl.stats).Elem(),
}
default:
return nil
}
}
var _ inspectInner = (*ethInfoInspectImpl)(nil)
type ethInfoInspectImpl struct {
name string
value *eth.Client
}
func (impl *ethInfoInspectImpl) ReadData() inspect.Object {
return inspect.Object{
Name: impl.name,
Properties: []inspect.Property{
{Key: "Topopath", Value: inspect.PropertyValueWithStr(impl.value.Topopath())},
{Key: "Filepath", Value: inspect.PropertyValueWithStr(impl.value.Filepath())},
{Key: "Features", Value: inspect.PropertyValueWithStr(featuresString(impl.value.Info.Features))},
},
}
}
func (impl *ethInfoInspectImpl) ListChildren() []string {
return []string{
rxReads,
rxWrites,
txReads,
txWrites,
}
}
func (impl *ethInfoInspectImpl) GetChild(childName string) inspectInner {
switch childName {
case rxReads:
return &fifoStatsInspectImpl{
name: childName,
value: impl.value.Stats.Rx.Reads,
size: impl.value.Stats.Rx.Size(),
}
case rxWrites:
return &fifoStatsInspectImpl{
name: childName,
value: impl.value.Stats.Rx.Writes,
size: impl.value.Stats.Rx.Size(),
}
case txReads:
return &fifoStatsInspectImpl{
name: childName,
value: impl.value.Stats.Tx.Reads,
size: impl.value.Stats.Tx.Size(),
}
case txWrites:
return &fifoStatsInspectImpl{
name: childName,
value: impl.value.Stats.Tx.Writes,
size: impl.value.Stats.Tx.Size(),
}
default:
return nil
}
}
var _ inspectInner = (*fifoStatsInspectImpl)(nil)
type fifoStatsInspectImpl struct {
name string
value func(uint32) *tcpip.StatCounter
size uint32
}
func (impl *fifoStatsInspectImpl) ReadData() inspect.Object {
var metrics []inspect.Metric
for i := uint32(0); i < impl.size; i++ {
batchSize := i + 1
if v := impl.value(batchSize).Value(); v != 0 {
metrics = append(metrics, inspect.Metric{
Key: strconv.FormatInt(int64(batchSize), 10),
Value: inspect.MetricValueWithUintValue(v),
})
}
}
return inspect.Object{
Name: impl.name,
Metrics: metrics,
}
}
func (impl *fifoStatsInspectImpl) ListChildren() []string {
return nil
}
func (impl *fifoStatsInspectImpl) GetChild(string) inspectInner {
return nil
}
var _ inspectInner = (*socketInfoMapInspectImpl)(nil)
type socketInfoMapInspectImpl struct {
value *endpointsMap
}
func (impl *socketInfoMapInspectImpl) ReadData() inspect.Object {
return inspect.Object{
Name: socketInfo,
}
}
func (impl *socketInfoMapInspectImpl) ListChildren() []string {
var children []string
impl.value.Range(func(handle zx.Handle, value tcpip.Endpoint) bool {
children = append(children, strconv.FormatUint(uint64(handle), 10))
return true
})
return children
}
func (impl *socketInfoMapInspectImpl) GetChild(childName string) inspectInner {
id, err := strconv.ParseUint(childName, 10, 64)
if err != nil {
syslog.VLogTf(syslog.DebugVerbosity, inspect.InspectName, "GetChild: %s", err)
return nil
}
if ep, ok := impl.value.Load(zx.Handle(id)); ok {
return &socketInfoInspectImpl{
name: childName,
info: ep.Info(),
state: ep.State(),
stats: ep.Stats(),
}
}
return nil
}
var _ inspectInner = (*socketInfoInspectImpl)(nil)
type socketInfoInspectImpl struct {
name string
info tcpip.EndpointInfo
state uint32
stats tcpip.EndpointStats
}
func (impl *socketInfoInspectImpl) ReadData() inspect.Object {
var common stack.TransportEndpointInfo
var hardError *tcpip.Error
switch t := impl.info.(type) {
case *tcp.EndpointInfo:
common = t.TransportEndpointInfo
hardError = t.HardError
case *stack.TransportEndpointInfo:
common = *t
default:
return inspect.Object{
Name: impl.name,
}
}
var netString string
switch common.NetProto {
case header.IPv4ProtocolNumber:
netString = "IPv4"
case header.IPv6ProtocolNumber:
netString = "IPv6"
default:
netString = "UNKNOWN"
}
var transString string
var state string
switch common.TransProto {
case header.TCPProtocolNumber:
transString = "TCP"
state = tcp.EndpointState(impl.state).String()
case header.UDPProtocolNumber:
transString = "UDP"
state = udp.EndpointState(impl.state).String()
case header.ICMPv4ProtocolNumber:
transString = "ICMPv4"
case header.ICMPv6ProtocolNumber:
transString = "ICMPv6"
default:
transString = "UNKNOWN"
}
localAddr := fmt.Sprintf("%s:%d", common.ID.LocalAddress.String(), common.ID.LocalPort)
remoteAddr := fmt.Sprintf("%s:%d", common.ID.RemoteAddress.String(), common.ID.RemotePort)
properties := []inspect.Property{
{Key: "NetworkProtocol", Value: inspect.PropertyValueWithStr(netString)},
{Key: "TransportProtocol", Value: inspect.PropertyValueWithStr(transString)},
{Key: "State", Value: inspect.PropertyValueWithStr(state)},
{Key: "LocalAddress", Value: inspect.PropertyValueWithStr(localAddr)},
{Key: "RemoteAddress", Value: inspect.PropertyValueWithStr(remoteAddr)},
{Key: "BindAddress", Value: inspect.PropertyValueWithStr(common.BindAddr.String())},
{Key: "BindNICID", Value: inspect.PropertyValueWithStr(strconv.FormatInt(int64(common.BindNICID), 10))},
{Key: "RegisterNICID", Value: inspect.PropertyValueWithStr(strconv.FormatInt(int64(common.RegisterNICID), 10))},
}
if hardError != nil {
properties = append(properties, inspect.Property{Key: "HardError", Value: inspect.PropertyValueWithStr(hardError.String())})
}
return inspect.Object{
Name: impl.name,
Properties: properties,
}
}
func (impl *socketInfoInspectImpl) ListChildren() []string {
return []string{
statsLabel,
}
}
func (impl *socketInfoInspectImpl) GetChild(childName string) inspectInner {
switch childName {
case statsLabel:
var value reflect.Value
switch t := impl.stats.(type) {
case *tcp.Stats:
value = reflect.ValueOf(t).Elem()
case *tcpip.TransportEndpointStats:
value = reflect.ValueOf(t).Elem()
default:
return nil
}
return &statCounterInspectImpl{
name: childName,
value: value,
}
}
return nil
}
func featuresString(v uint32) string {
switch v {
case 0:
return "[none]"
case ethernet.InfoFeatureWlan:
return "WLAN"
case ethernet.InfoFeatureSynth:
return "SYNTHETIC"
case ethernet.InfoFeatureLoopback:
return "LOOPBACK"
default:
return "[unknown]"
}
}