blob: 8e0d8b90f2442a6ad03dae534413b43b2fead18a [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.
//go:build !build_with_native_toolchain
package netstack
import (
"context"
"errors"
"fmt"
"reflect"
"sort"
"strconv"
"syscall/zx"
"testing"
"time"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/dhcp"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlconv"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/routetypes"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/util"
inspect "fidl/fuchsia/inspect/deprecated"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/arp"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
)
var (
ipv4Addr = util.Parse("192.168.1.0")
ipv6Addr = util.Parse("a::1")
)
type inspectNodeExpectation struct {
node inspect.Object
children []inspectNodeExpectation
}
func checkInspectRecurse(node inspectInner, expected inspectNodeExpectation) error {
var err error
nodeData := node.ReadData()
if nodeData.Name != expected.node.Name {
err = errors.Join(err, fmt.Errorf("found unexpected name %s instead of %s", nodeData.Name, expected.node.Name))
}
if diff := cmp.Diff(expected.node.Properties, nodeData.Properties, cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Metric{})); diff != "" {
err = errors.Join(err, fmt.Errorf("Properties mismatch (-want +got):\n%s", diff))
}
containsMetric := func(metrics []inspect.Metric, metricToFind inspect.Metric) bool {
for _, metric := range metrics {
if metric == metricToFind {
return true
}
}
return false
}
for _, metric := range nodeData.Metrics {
if metric.Value != inspect.MetricValueWithUintValue(0) && !containsMetric(expected.node.Metrics, metric) {
err = errors.Join(err, fmt.Errorf("ReadData() mismatch: found unexpected non-zero metric %#v", metric))
}
}
for _, metric := range expected.node.Metrics {
if !containsMetric(nodeData.Metrics, metric) {
err = errors.Join(err, fmt.Errorf("ReadData() mismatch: missing expected non-zero metric %#v", metric))
}
}
children := node.ListChildren()
var expectedChildrenNames []string
for _, child := range expected.children {
expectedChildrenNames = append(expectedChildrenNames, child.node.Name)
}
if diff := cmp.Diff(expectedChildrenNames, children, cmpopts.SortSlices(func(a, b string) bool {
return a < b
})); diff != "" {
err = errors.Join(err, fmt.Errorf("ListChildren() mismatch (-want +got):\n%s", diff))
}
childName := "not a real child"
if child := node.GetChild(childName); child != nil {
err = errors.Join(err, fmt.Errorf("got GetChild(%s) = %s, want = nil", childName, child))
}
for i, childName := range expectedChildrenNames {
if child := node.GetChild(childName); child != nil {
err = errors.Join(err, checkInspectRecurse(child, expected.children[i]))
} else {
err = errors.Join(err, fmt.Errorf("got GetChild(%s) = nil, want non-nil", childName))
}
}
return err
}
func TestStatCounterInspectImpl(t *testing.T) {
const invalidPort = 1
const invalidPortCount = 10
const initAcquireCount = 3
var s dhcp.Stats
s.PacketDiscardStats.Init()
for i := 0; i < invalidPortCount; i++ {
s.PacketDiscardStats.InvalidPort.Increment(invalidPort)
}
s.InitAcquire.IncrementBy(initAcquireCount)
v := statCounterInspectImpl{
name: "doesn't matter",
value: reflect.ValueOf(&s).Elem(),
}
expected := inspectNodeExpectation{
node: inspect.Object{
Name: "doesn't matter",
Metrics: []inspect.Metric{
{Key: "InitAcquire", Value: inspect.MetricValueWithUintValue(initAcquireCount)},
},
},
children: []inspectNodeExpectation{
{
node: inspect.Object{
Name: "PacketDiscardStats",
},
children: []inspectNodeExpectation{
{
node: inspect.Object{
Name: "InvalidPort",
},
children: []inspectNodeExpectation{
{
node: inspect.Object{
Name: "1",
Properties: []inspect.Property{
{Key: "Count", Value: inspect.PropertyValueWithStr("10")},
},
},
},
{
node: inspect.Object{
Name: "Total",
Properties: []inspect.Property{
{Key: "Count", Value: inspect.PropertyValueWithStr("10")},
},
},
},
},
},
{
node: inspect.Object{
Name: "InvalidTransProto",
},
children: []inspectNodeExpectation{
{
node: inspect.Object{
Name: "Total",
Properties: []inspect.Property{
{Key: "Count", Value: inspect.PropertyValueWithStr("0")},
},
},
},
},
},
{
node: inspect.Object{
Name: "InvalidPacketType",
},
children: []inspectNodeExpectation{
{
node: inspect.Object{
Name: "Total",
Properties: []inspect.Property{
{Key: "Count", Value: inspect.PropertyValueWithStr("0")},
},
},
},
},
},
},
},
},
}
if err := checkInspectRecurse(&v, expected); err != nil {
t.Error(err)
}
}
func TestStatCounterGetChildDoesNotCopy(t *testing.T) {
const fieldName = "InvalidPort"
var s dhcp.PacketDiscardStats
s.Init()
v := statCounterInspectImpl{
name: "doesn't matter",
value: reflect.ValueOf(&s).Elem(),
}
child := v.GetChild(fieldName)
integralMap, ok := child.(*integralStatCounterMapInspectImpl)
if !ok {
t.Fatalf("got v.GetChild(%q) = %#v, want %T", fieldName, child, integralMap)
}
if want, got := s.InvalidPort, integralMap.value; want != got {
t.Errorf("got integralMap.value = %p, want %p", got, want)
}
}
func circularLogsChecker(logs *circularLogsInspectImpl, expectedChildren []string, expectedChildrenData []inspect.Object) []error {
var errors []error
if diff := cmp.Diff(inspect.Object{
Name: logs.name,
}, logs.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Metric{})); diff != "" {
errors = append(errors, fmt.Errorf("ReadData() mismatch (-want +got):\n%s", diff))
}
children := logs.ListChildren()
if diff := cmp.Diff(expectedChildren, children); diff != "" {
errors = append(errors, fmt.Errorf("ListChildren() mismatch (-want +got):\n%s", diff))
return errors
}
childName := "not a real child"
if child := logs.GetChild(childName); child != nil {
errors = append(errors, fmt.Errorf("got GetChild(%s) = %s, want = nil", childName, child))
}
for i, childName := range children {
if child := logs.GetChild(childName); child == nil {
errors = append(errors, fmt.Errorf("got GetChild(%s) = nil, want non-nil", childName))
} else if entry, ok := child.(*logEntryInspectImpl); !ok {
errors = append(errors, fmt.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*statCounterInspectImpl)(nil)))
} else {
if diff := cmp.Diff(expectedChildrenData[i], entry.ReadData(), cmpopts.IgnoreUnexported(
inspect.Object{}, inspect.Metric{})); diff != "" {
errors = append(errors, fmt.Errorf("ReadData() mismatch (-want +got):\n%s", diff))
}
if diff := cmp.Diff(child.ListChildren(), []string(nil)); diff != "" {
errors = append(errors, fmt.Errorf("ListChildren() mismatch (-want +got):\n%s", diff))
}
}
}
return errors
}
func TestCircularLogsInspectImpl(t *testing.T) {
v := circularLogsInspectImpl{
name: "dosn't matter",
value: []util.LogEntry{
{Timestamp: 42, Content: "foo"},
{Timestamp: 1337, Content: "bar"},
},
}
expectedChildren := []string{"0", "1"}
expectedChildrenData := []inspect.Object{
{
Name: "0",
Properties: []inspect.Property{
{Key: "@time", Value: inspect.PropertyValueWithStr("42")},
{Key: "value", Value: inspect.PropertyValueWithStr("foo")},
},
},
{
Name: "1",
Properties: []inspect.Property{
{Key: "@time", Value: inspect.PropertyValueWithStr("1337")},
{Key: "value", Value: inspect.PropertyValueWithStr("bar")},
},
},
}
errors := circularLogsChecker(&v, expectedChildren, expectedChildrenData)
for _, err := range errors {
t.Error(err)
}
}
func TestIntegralStatCounterMapInspectImpl(t *testing.T) {
var integralStatCounterMap tcpip.IntegralStatCounterMap
integralStatCounterMap.Init()
const firstKey = 1
const firstValue = 10
for i := 0; i < firstValue; i++ {
integralStatCounterMap.Increment(firstKey)
}
const secondKey = 2
const secondValue = 20
for i := 0; i < secondValue; i++ {
integralStatCounterMap.Increment(secondKey)
}
v := integralStatCounterMapInspectImpl{
name: "doesn't matter",
value: &integralStatCounterMap,
}
expected := inspectNodeExpectation{
node: inspect.Object{
Name: "doesn't matter",
},
children: []inspectNodeExpectation{
{
node: inspect.Object{
Name: strconv.Itoa(firstKey),
Properties: []inspect.Property{
{Key: "Count", Value: inspect.PropertyValueWithStr("10")},
},
},
},
{
node: inspect.Object{
Name: strconv.Itoa(secondKey),
Properties: []inspect.Property{
{Key: "Count", Value: inspect.PropertyValueWithStr("20")},
},
},
},
{
node: inspect.Object{
Name: "Total",
Properties: []inspect.Property{
{Key: "Count", Value: inspect.PropertyValueWithStr("30")},
},
},
},
},
}
if err := checkInspectRecurse(&v, expected); err != nil {
t.Error(err)
}
}
func TestSocketStatCounterInspectImpl(t *testing.T) {
// Create a new netstack and add TCP and UDP endpoints.
ns, _ := newNetstack(t, netstackTestOptions{})
wq := new(waiter.Queue)
v := socketInfoMapInspectImpl{
value: &ns.endpoints,
}
children := v.ListChildren()
if diff := cmp.Diff([]string(nil), children); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
// Add a TCP socket. Since it's the first to be added, it will have key = 1.
const tcpKey = 1
tcpEP, tcpipErr := ns.stack.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
if tcpipErr != nil {
t.Fatal(tcpipErr)
}
tcpEPS, err := newEndpointWithSocket(tcpEP, wq, tcp.ProtocolNumber, ipv4.ProtocolNumber, ns, zx.SocketStream)
if err != nil {
t.Fatalf("failed to create TCP endpointWithSocket: %s", err)
}
// The following call is necessary in order for the TCP-specific socket option
// stats to be registered.
_ = makeStreamSocketImpl(tcpEPS)
// Add a UDP socket.
const udpKey = 2
udpEP, tcpipErr := ns.stack.NewEndpoint(udp.ProtocolNumber, ipv6.ProtocolNumber, wq)
if tcpipErr != nil {
t.Fatal(tcpipErr)
}
// The following call is necessary for socket option stats to be registered.
if _, err := newEndpointWithSocket(udpEP, wq, udp.ProtocolNumber, ipv6.ProtocolNumber, ns, zx.SocketDatagram); err != nil {
t.Fatalf("failed to create UDP endpointWithSocket: %s", err)
}
children = v.ListChildren()
sort.Strings(children)
if diff := cmp.Diff([]string{strconv.Itoa(tcpKey), strconv.Itoa(udpKey)}, children); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
childName := "not a real child"
if child := v.GetChild(childName); child != nil {
t.Errorf("got GetChild(%s) = %s, want = nil", childName, child)
}
for _, name := range children {
child := v.GetChild(name)
if child == nil {
t.Fatalf("got GetChild(%s) = nil, want non-nil", name)
}
val, err := strconv.ParseUint(name, 10, 64)
if err != nil {
t.Fatalf("string parsing error %s", err)
}
// Update protocol specific expected values.
var (
networkProtoName, transportProtoName string
unspecifiedAddress string
expectStatsObject inspect.Object
expectSockOptStatsType reflect.Type
)
switch val {
case tcpKey:
networkProtoName = "IPv4"
transportProtoName = "TCP"
unspecifiedAddress = "0.0.0.0"
expectStatsObject = inspect.Object{
Name: statsLabel,
Metrics: []inspect.Metric{
{Key: "SegmentsReceived", Value: inspect.MetricValueWithUintValue(0)},
{Key: "SegmentsSent", Value: inspect.MetricValueWithUintValue(0)},
{Key: "FailedConnectionAttempts", Value: inspect.MetricValueWithUintValue(0)},
},
}
expectSockOptStatsType = reflect.TypeOf((*streamSocketOptionStats)(nil)).Elem()
case udpKey:
networkProtoName = "IPv6"
transportProtoName = "UDP"
unspecifiedAddress = "[::]"
expectStatsObject = inspect.Object{
Name: statsLabel,
Metrics: []inspect.Metric{
{Key: "PacketsReceived", Value: inspect.MetricValueWithUintValue(0)},
{Key: "PacketsSent", Value: inspect.MetricValueWithUintValue(0)},
},
}
expectSockOptStatsType = reflect.TypeOf((*NetworkSocketOptionStats)(nil)).Elem()
}
ignoreUnexportedInspect := cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Metric{}, inspect.Property{})
epChildren := child.ListChildren()
if diff := cmp.Diff([]string{
statsLabel, socketOptionStatsLabel,
}, epChildren); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
for _, c := range epChildren {
switch c {
case statsLabel:
statsChild := child.GetChild(c)
if diff := cmp.Diff([]string{
"ReceiveErrors",
"ReadErrors",
"SendErrors",
"WriteErrors",
}, statsChild.ListChildren()); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
// Compare against the expected inspect objects.
if diff := cmp.Diff(expectStatsObject, statsChild.ReadData(), ignoreUnexportedInspect); diff != "" {
t.Errorf("ReadData() mismatch (-want +got):\n%s", diff)
}
case socketOptionStatsLabel:
sockOptStats := child.GetChild(c)
var want []inspect.Metric
for _, field := range reflect.VisibleFields(expectSockOptStatsType) {
// Must exclude the anonymous fields that contain stat counters since
// they're not stat counters themselves.
if field.Anonymous {
continue
}
want = append(want, inspect.Metric{Key: field.Name, Value: inspect.MetricValueWithUintValue(0)})
}
sortMetricByName := func(a, b inspect.Metric) bool {
return a.Key < b.Key
}
if diff := cmp.Diff(want, sockOptStats.ReadData().Metrics, ignoreUnexportedInspect, cmpopts.SortSlices(sortMetricByName)); diff != "" {
t.Errorf("SocketOptionStats ReadData() mismatch (-want +got):\n%s", diff)
}
default:
t.Errorf("unexpected child: %s", c)
}
}
if diff := cmp.Diff(inspect.Object{
Name: name,
Properties: []inspect.Property{
{Key: "NetworkProtocol", Value: inspect.PropertyValueWithStr(networkProtoName)},
{Key: "TransportProtocol", Value: inspect.PropertyValueWithStr(transportProtoName)},
{Key: "State", Value: inspect.PropertyValueWithStr("INITIAL")},
{Key: "LocalAddress", Value: inspect.PropertyValueWithStr(unspecifiedAddress + ":0")},
{Key: "RemoteAddress", Value: inspect.PropertyValueWithStr(unspecifiedAddress + ":0")},
{Key: "BindAddress", Value: inspect.PropertyValueWithStr("")},
{Key: "BindNICID", Value: inspect.PropertyValueWithStr("0")},
{Key: "RegisterNICID", Value: inspect.PropertyValueWithStr("0")},
},
}, child.ReadData(), ignoreUnexportedInspect); diff != "" {
t.Errorf("ReadData() mismatch (-want +got):\n%s", diff)
}
}
// Empty the endpoints.
ns.endpoints.Range(func(key uint64, _ endpointAndStats) bool {
ns.endpoints.Delete(key)
return true
})
children = v.ListChildren()
if diff := cmp.Diff([]string(nil), children); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
}
func TestNicInfoMapInspectImpl(t *testing.T) {
v := nicInfoMapInspectImpl{
value: map[tcpip.NICID]ifStateInfo{
1: {},
2: {},
},
}
children := v.ListChildren()
if diff := cmp.Diff([]string{
"1", "2",
}, children); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
for _, childName := range children {
if child := v.GetChild(childName); child == nil {
t.Errorf("got GetChild(%s) = nil, want non-nil", childName)
} else if _, ok := child.(*nicInfoInspectImpl); !ok {
t.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*nicInfoInspectImpl)(nil))
}
}
childName := "not a real child"
if child := v.GetChild(childName); child != nil {
t.Errorf("got GetChild(%s) = %s, want = nil", childName, child)
}
if diff := cmp.Diff(inspect.Object{
Name: "NICs",
}, v.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{})); diff != "" {
t.Errorf("ReadData() mismatch (-want +got):\n%s", diff)
}
}
func TestNicInfoInspectImpl(t *testing.T) {
v := nicInfoInspectImpl{
name: "doesn't matter",
}
children := v.ListChildren()
if diff := cmp.Diff([]string{
"Stats",
}, children); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
for _, childName := range children {
if child := v.GetChild(childName); child == nil {
t.Errorf("got GetChild(%s) = nil, want non-nil", childName)
} else if _, ok := child.(*statCounterInspectImpl); !ok {
t.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*statCounterInspectImpl)(nil))
}
}
childName := "not a real child"
if child := v.GetChild(childName); child != nil {
t.Errorf("got GetChild(%s) = %s, want = nil", childName, child)
}
v.value.nicid = 5
v.value.Flags.Up = true
v.value.Flags.Loopback = true
v.value.dnsServers = []tcpip.Address{ipv4Addr}
if diff := cmp.Diff(inspect.Object{
Name: v.name,
Properties: []inspect.Property{
{Key: "Name", Value: inspect.PropertyValueWithStr(v.value.Name)},
{Key: "NICID", Value: inspect.PropertyValueWithStr("5")},
{Key: "AdminUp", Value: inspect.PropertyValueWithStr("false")},
{Key: "LinkOnline", Value: inspect.PropertyValueWithStr("false")},
{Key: "Up", Value: inspect.PropertyValueWithStr("true")},
{Key: "Running", Value: inspect.PropertyValueWithStr("false")},
{Key: "Loopback", Value: inspect.PropertyValueWithStr("true")},
{Key: "Promiscuous", Value: inspect.PropertyValueWithStr("false")},
{Key: "DNS server0", Value: inspect.PropertyValueWithStr(ipv4Addr.String())},
{Key: "DHCP enabled", Value: inspect.PropertyValueWithStr("false")},
},
Metrics: []inspect.Metric{
{Key: "MTU", Value: inspect.MetricValueWithUintValue(uint64(v.value.MTU))},
},
}, v.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Property{}, inspect.Metric{})); diff != "" {
t.Errorf("ReadData() mismatch (-want +got):\n%s", diff)
}
}
func TestDHCPInfoInspectImpl(t *testing.T) {
var invalidPortCounter, invalidTransProtoCounter, invalidPacketTypeCounter, invalidPacketType2Counter tcpip.StatCounter
const invalidPort = 1
const invalidPortCount = 10
invalidPortCounter.IncrementBy(invalidPortCount)
const invalidTransProto = 2
const invalidTransProtoCount = 20
invalidTransProtoCounter.IncrementBy(invalidTransProtoCount)
const invalidPacketType = 3
const invalidPacketTypeCount = 30
invalidPacketTypeCounter.IncrementBy(invalidPacketTypeCount)
const invalidPacketType2 = 4
const invalidPacketType2Count = 40
invalidPacketType2Counter.IncrementBy(invalidPacketType2Count)
const counterPropertyKey = "Count"
v := dhcpInfoInspectImpl{
name: "doesn't matter",
stateRecentHistory: []util.LogEntry{
{Timestamp: 1, Content: "1"},
{Timestamp: 2, Content: "2"},
},
stats: &dhcp.Stats{},
}
v.stats.PacketDiscardStats.Init()
for i := 0; i < invalidPortCount; i++ {
v.stats.PacketDiscardStats.InvalidPort.Increment(invalidPort)
}
for i := 0; i < invalidTransProtoCount; i++ {
v.stats.PacketDiscardStats.InvalidTransProto.Increment(invalidTransProto)
}
for i := 0; i < invalidPacketTypeCount; i++ {
v.stats.PacketDiscardStats.InvalidPacketType.Increment(invalidPacketType)
}
for i := 0; i < invalidPacketType2Count; i++ {
v.stats.PacketDiscardStats.InvalidPacketType.Increment(invalidPacketType2)
}
children := v.ListChildren()
if diff := cmp.Diff([]string{
"Stats", "DHCP State Recent History",
}, children); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
{
// Validate Stats struct.
child := v.GetChild("Stats")
stats, ok := child.(*statCounterInspectImpl)
if !ok {
t.Fatalf("got GetChild(Stats) = %#v, want %T", child, (*statCounterInspectImpl)(nil))
}
expected := inspectNodeExpectation{
node: inspect.Object{
Name: "Stats",
},
children: []inspectNodeExpectation{
{
node: inspect.Object{
Name: "PacketDiscardStats",
},
children: []inspectNodeExpectation{
{
node: inspect.Object{
Name: "InvalidPort",
},
children: []inspectNodeExpectation{
{
node: inspect.Object{
Name: strconv.Itoa(invalidPort),
Properties: []inspect.Property{
{Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidPortCounter.Value(), 10))},
},
},
},
{
node: inspect.Object{
Name: "Total",
Properties: []inspect.Property{
{Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidPortCounter.Value(), 10))},
},
},
},
},
},
{
node: inspect.Object{
Name: "InvalidTransProto",
},
children: []inspectNodeExpectation{
{
node: inspect.Object{
Name: strconv.Itoa(invalidTransProto),
Properties: []inspect.Property{
{Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidTransProtoCounter.Value(), 10))},
},
},
},
{
node: inspect.Object{
Name: "Total",
Properties: []inspect.Property{
{Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidTransProtoCounter.Value(), 10))},
},
},
},
},
},
{
node: inspect.Object{
Name: "InvalidPacketType",
},
children: []inspectNodeExpectation{
{
node: inspect.Object{
Name: strconv.Itoa(invalidPacketType),
Properties: []inspect.Property{
{Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidPacketTypeCounter.Value(), 10))},
},
},
},
{
node: inspect.Object{
Name: strconv.Itoa(invalidPacketType2),
Properties: []inspect.Property{
{Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidPacketType2Counter.Value(), 10))},
},
},
},
{
node: inspect.Object{
Name: "Total",
Properties: []inspect.Property{
{Key: counterPropertyKey, Value: inspect.PropertyValueWithStr(strconv.FormatUint(invalidPacketTypeCounter.Value()+invalidPacketType2Counter.Value(), 10))},
},
},
},
},
},
},
},
},
}
if err := checkInspectRecurse(stats, expected); err != nil {
t.Error(err)
}
}
childName := "not a real child"
if child := v.GetChild(childName); child != nil {
t.Errorf("got GetChild(%s) = %s, want = nil", childName, child)
}
v.info.Config.Router = []tcpip.Address{ipv4Addr}
v.info.Config.DNS = []tcpip.Address{ipv4Addr}
if diff := cmp.Diff(inspect.Object{
Name: v.name,
Properties: []inspect.Property{
{Key: "State", Value: inspect.PropertyValueWithStr("initSelecting")},
{Key: "AcquiredAddress", Value: inspect.PropertyValueWithStr("[none]")},
{Key: "AssignedAddress", Value: inspect.PropertyValueWithStr("[none]")},
{Key: "Acquisition", Value: inspect.PropertyValueWithStr("0s")},
{Key: "Backoff", Value: inspect.PropertyValueWithStr("0s")},
{Key: "Retransmission", Value: inspect.PropertyValueWithStr("0s")},
{Key: "LeaseExpiration", Value: inspect.PropertyValueWithStr("m=+0.000000000")},
{Key: "RenewTime", Value: inspect.PropertyValueWithStr("m=+0.000000000")},
{Key: "RebindTime", Value: inspect.PropertyValueWithStr("m=+0.000000000")},
{Key: "Config.ServerAddress", Value: inspect.PropertyValueWithStr("[none]")},
{Key: "Config.SubnetMask", Value: inspect.PropertyValueWithStr("[none]")},
{Key: "Config.Router0", Value: inspect.PropertyValueWithStr(ipv4Addr.String())},
{Key: "Config.DNS0", Value: inspect.PropertyValueWithStr(ipv4Addr.String())},
{Key: "Config.LeaseLength", Value: inspect.PropertyValueWithStr("0s")},
{Key: "Config.RenewTime", Value: inspect.PropertyValueWithStr("0s")},
{Key: "Config.RebindTime", Value: inspect.PropertyValueWithStr("0s")},
{Key: "Config.Declined", Value: inspect.PropertyValueWithStr("false")},
},
}, v.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Property{}, inspect.Metric{})); diff != "" {
t.Errorf("ReadData() mismatch (-want +got):\n%s", diff)
}
{
child := v.GetChild("DHCP State Recent History")
recentHistory, ok := child.(*circularLogsInspectImpl)
if !ok {
t.Errorf("got GetChild(DHCP State Recent History) %#v, want %T", child, (*statCounterInspectImpl)(nil))
}
if recentHistory != nil {
expectedChildren := []string{"0", "1"}
expectedChildrenData := []inspect.Object{
{
Name: "0",
Properties: []inspect.Property{
{Key: "@time", Value: inspect.PropertyValueWithStr("1")},
{Key: "value", Value: inspect.PropertyValueWithStr("1")},
},
},
{
Name: "1",
Properties: []inspect.Property{
{Key: "@time", Value: inspect.PropertyValueWithStr("2")},
{Key: "value", Value: inspect.PropertyValueWithStr("2")},
},
},
}
errors := circularLogsChecker(recentHistory, expectedChildren, expectedChildrenData)
for _, err := range errors {
t.Error(err)
}
}
}
}
func TestFifoStatsInfoInspectImpl(t *testing.T) {
tests := []struct {
name string
impl func() fifoStatsInspectImpl
wantData func() inspect.Object
}{
{
name: "size 2",
impl: func() fifoStatsInspectImpl {
var zeroCounter, nonZeroCounter tcpip.StatCounter
nonZeroCounter.IncrementBy(5)
return fifoStatsInspectImpl{
name: "size 2",
value: func(batch uint32) *tcpip.StatCounter {
if batch%2 == 0 {
return &zeroCounter
}
return &nonZeroCounter
},
size: 2,
}
},
wantData: func() inspect.Object {
return inspect.Object{
Name: "size 2",
Metrics: []inspect.Metric{
{Key: "1", Value: inspect.MetricValueWithUintValue(5)},
},
}
},
},
{
name: "size 2001",
impl: func() fifoStatsInspectImpl {
var zeroCounter, nonZeroCounter tcpip.StatCounter
nonZeroCounter.IncrementBy(5)
return fifoStatsInspectImpl{
name: "size 2001",
value: func(batch uint32) *tcpip.StatCounter {
if batch == 1 || batch == 2000 || batch == 2001 {
return &nonZeroCounter
}
return &zeroCounter
},
size: 2001,
}
},
wantData: func() inspect.Object {
return inspect.Object{
Name: "size 2001",
Metrics: []inspect.Metric{
{
Key: "1-2",
Value: inspect.MetricValueWithUintValue(5),
},
{
Key: "1999-2000",
Value: inspect.MetricValueWithUintValue(5),
},
{
Key: "2001",
Value: inspect.MetricValueWithUintValue(5),
},
},
}
},
},
{
name: "size 2048",
impl: func() fifoStatsInspectImpl {
var nonZeroCounter tcpip.StatCounter
nonZeroCounter.IncrementBy(5)
return fifoStatsInspectImpl{
name: "size 2048",
value: func(_ uint32) *tcpip.StatCounter {
return &nonZeroCounter
},
size: 2048,
}
},
wantData: func() inspect.Object {
metrics := []inspect.Metric{}
for i := 0; i < 2048; i = i + 2 {
metrics = append(metrics, inspect.Metric{
Key: fmt.Sprintf("%d-%d", i+1, i+2),
Value: inspect.MetricValueWithUintValue(10),
})
}
return inspect.Object{
Name: "size 2048",
Metrics: metrics,
}
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
v := test.impl()
// ListChildren always returns nil.
children := v.ListChildren()
if diff := cmp.Diff(children, []string(nil)); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
// GetChild always returns nil.
childName := "not a real child"
if child := v.GetChild(childName); child != nil {
t.Errorf("got GetChild(%s) = %s, want = nil", childName, child)
}
data := v.ReadData()
if l := len(data.Metrics); l > maxMetricsForFifoStats {
t.Errorf("the length of Metrics (%d) exceeds maxMetricsForFifoStats(%d)", l, maxMetricsForFifoStats)
}
if diff := cmp.Diff(test.wantData(), data, cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Property{}, inspect.Metric{})); diff != "" {
t.Errorf("ReadData() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestInspectGetMissingChild(t *testing.T) {
impl := inspectImpl{
inner: &nicInfoMapInspectImpl{},
}
req, proxy, err := inspect.NewInspectWithCtxInterfaceRequest()
if err != nil {
t.Fatalf("inspect.NewInspectWithCtxInterfaceRequest() = %s", err)
}
defer func() {
if err := proxy.Close(); err != nil {
t.Errorf("proxy.Close() = %s", err)
}
}()
found, err := impl.OpenChild(context.Background(), "non-existing-child", req)
if err != nil {
t.Fatalf("impl.OpenChild(...) = %s", err)
}
if found {
t.Fatalf("got impl.OpenChild(...) = true, want = false")
}
// The request channel must have been closed.
if status := zx.Sys_object_wait_one(*proxy.Channel.Handle(), zx.SignalChannelPeerClosed, 0, nil); status != zx.ErrOk {
t.Fatalf("zx.Sys_object_wait_one(_, zx.SignalChannelPeerClosed, 0, nil) = %s", status)
}
}
func TestRoutingTableInspectImpl(t *testing.T) {
impl := routingTableInspectImpl{
value: []routetypes.ExtendedRoute{
{}, {},
},
}
children := impl.ListChildren()
if diff := cmp.Diff([]string{
"0", "1",
}, children); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
for _, childName := range children {
if child := impl.GetChild(childName); child == nil {
t.Errorf("got GetChild(%s) = nil, want non-nil", childName)
} else if _, ok := child.(*routeInfoInspectImpl); !ok {
t.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*routeInfoInspectImpl)(nil))
}
}
childName := "not a real child"
if got := impl.GetChild(childName); got != nil {
t.Errorf("got GetChild(%s) = %s, want = nil", childName, got)
}
// Index past the end of the routing table
childName = "2"
if got := impl.GetChild(childName); got != nil {
t.Errorf("got GetChild(%s) = %s, want = nil", childName, got)
}
if diff := cmp.Diff(inspect.Object{
Name: "Routes",
}, impl.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{})); diff != "" {
t.Errorf("ReadData() mismatch (-want +got):\n%s", diff)
}
}
func TestRouteInfoInspectImpl(t *testing.T) {
tests := []struct {
name string
route routetypes.ExtendedRoute
properties []inspect.Property
}{
{
name: "IPv4",
route: routetypes.ExtendedRoute{
Route: tcpip.Route{
Destination: header.IPv4EmptySubnet,
Gateway: util.Parse("1.2.3.4"),
NIC: 1,
},
Metric: 42,
MetricTracksInterface: true,
Dynamic: true,
Enabled: true,
},
properties: []inspect.Property{
{Key: "Destination", Value: inspect.PropertyValueWithStr("0.0.0.0/0")},
{Key: "Gateway", Value: inspect.PropertyValueWithStr("1.2.3.4")},
{Key: "NIC", Value: inspect.PropertyValueWithStr("1")},
{Key: "Metric", Value: inspect.PropertyValueWithStr("42")},
{Key: "MetricTracksInterface", Value: inspect.PropertyValueWithStr("true")},
{Key: "Dynamic", Value: inspect.PropertyValueWithStr("true")},
{Key: "Enabled", Value: inspect.PropertyValueWithStr("true")},
},
},
{
name: "IPv6",
route: routetypes.ExtendedRoute{
Route: tcpip.Route{
Destination: header.IPv6EmptySubnet,
Gateway: util.Parse("fe80::1"),
NIC: 2,
},
Metric: 0,
MetricTracksInterface: false,
Dynamic: true,
Enabled: true,
},
properties: []inspect.Property{
{Key: "Destination", Value: inspect.PropertyValueWithStr("::/0")},
{Key: "Gateway", Value: inspect.PropertyValueWithStr("fe80::1")},
{Key: "NIC", Value: inspect.PropertyValueWithStr("2")},
{Key: "Metric", Value: inspect.PropertyValueWithStr("0")},
{Key: "MetricTracksInterface", Value: inspect.PropertyValueWithStr("false")},
{Key: "Dynamic", Value: inspect.PropertyValueWithStr("true")},
{Key: "Enabled", Value: inspect.PropertyValueWithStr("true")},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
impl := routeInfoInspectImpl{name: "0", value: test.route}
children := impl.ListChildren()
if diff := cmp.Diff(children, []string(nil)); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
childName := "not a real child"
if got := impl.GetChild(childName); got != nil {
t.Errorf("got GetChild(%s) = %s, want = nil", childName, got)
}
if diff := cmp.Diff(
inspect.Object{Name: "0", Properties: test.properties},
impl.ReadData(),
cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Property{}, inspect.Metric{}),
); diff != "" {
t.Errorf("ReadData() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestNetworkEndpointStatsInspectImpl(t *testing.T) {
const unknownNetworkProtocolNumber = 0
impl := networkEndpointStatsInspectImpl{
name: "Network Endpoint Stats",
value: map[string]stack.NetworkEndpointStats{
"IPv4": &ipv4.Stats{},
"IPv6": &ipv6.Stats{},
"ARP": &arp.Stats{},
"Random Name": &arp.Stats{},
},
}
expectedProtocols := []string{"IPv4", "IPv6", "ARP", "Random Name"}
children := impl.ListChildren()
if diff := cmp.Diff(expectedProtocols, children, cmpopts.SortSlices(func(a, b string) bool {
return a < b
})); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
for _, childName := range children {
if child := impl.GetChild(childName); child == nil {
t.Errorf("got GetChild(%s) = nil, want non-nil", childName)
} else if _, ok := child.(*statCounterInspectImpl); !ok {
t.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*statCounterInspectImpl)(nil))
}
}
childName := "not a real child"
if got := impl.GetChild(childName); got != nil {
t.Errorf("got GetChild(%s) = %s, want = nil", childName, got)
}
if diff := cmp.Diff(inspect.Object{
Name: "Network Endpoint Stats",
}, impl.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{})); diff != "" {
t.Errorf("ReadData() mismatch (-want +got):\n%s", diff)
}
}
func TestNeighborTableInspectImpl(t *testing.T) {
someTime := tcpip.MonotonicTime{}
impl := neighborTableInspectImpl{
name: neighborsLabel,
value: map[string]stack.NeighborEntry{
ipv4Addr.String(): {
Addr: ipv4Addr,
LinkAddr: "\x0a\x00\x00\x00\x00\x01",
State: stack.Reachable,
UpdatedAt: someTime.Add(1 * time.Nanosecond),
},
ipv6Addr.String(): {
Addr: ipv6Addr,
LinkAddr: "\x0a\x00\x00\x00\x00\x02",
State: stack.Stale,
UpdatedAt: someTime.Add(2 * time.Nanosecond),
},
},
}
children := impl.ListChildren()
if diff := cmp.Diff([]string{
ipv4Addr.String(), ipv6Addr.String(),
}, children, cmpopts.SortSlices(func(a, b string) bool {
return a < b
})); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
for _, childName := range children {
if child := impl.GetChild(childName); child == nil {
t.Errorf("got GetChild(%s) = nil, want non-nil", childName)
} else if _, ok := child.(*neighborInfoInspectImpl); !ok {
t.Errorf("got GetChild(%s) = %#v, want %T", childName, child, (*neighborInfoInspectImpl)(nil))
}
}
childName := "not a real child"
if got := impl.GetChild(childName); got != nil {
t.Errorf("got GetChild(%s) = %s, want = nil", childName, got)
}
if diff := cmp.Diff(inspect.Object{
Name: neighborsLabel,
}, impl.ReadData(), cmpopts.IgnoreUnexported(inspect.Object{})); diff != "" {
t.Errorf("ReadData() mismatch (-want +got):\n%s", diff)
}
}
func TestNeighborInfoInspectImpl(t *testing.T) {
someTime := tcpip.MonotonicTime{}
tests := []struct {
name string
neighbor stack.NeighborEntry
}{
{
name: "IPv4",
neighbor: stack.NeighborEntry{
Addr: ipv4Addr,
LinkAddr: "\x0a\x00\x00\x00\x00\x01",
State: stack.Reachable,
UpdatedAt: someTime.Add(1 * time.Nanosecond),
},
},
{
name: "IPv6",
neighbor: stack.NeighborEntry{
Addr: ipv6Addr,
LinkAddr: "\x0a\x00\x00\x00\x00\x02",
State: stack.Stale,
UpdatedAt: someTime.Add(2 * time.Nanosecond),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
impl := neighborInfoInspectImpl{value: test.neighbor}
if diff := cmp.Diff(impl.ListChildren(), []string(nil)); diff != "" {
t.Errorf("ListChildren() mismatch (-want +got):\n%s", diff)
}
childName := "not a real child"
if got := impl.GetChild(childName); got != nil {
t.Errorf("got GetChild(%s) = %s, want = nil", childName, got)
}
if diff := cmp.Diff(
inspect.Object{
Name: test.neighbor.Addr.String(),
Properties: []inspect.Property{
{Key: "Link address", Value: inspect.PropertyValueWithStr(test.neighbor.LinkAddr.String())},
{Key: "State", Value: inspect.PropertyValueWithStr(test.neighbor.State.String())},
},
Metrics: []inspect.Metric{
{Key: "Last updated", Value: inspect.MetricValueWithIntValue(int64(fidlconv.ToZxTime(test.neighbor.UpdatedAt)))},
},
},
impl.ReadData(),
cmpopts.IgnoreUnexported(inspect.Object{}, inspect.Property{}, inspect.Metric{}),
); diff != "" {
t.Errorf("ReadData() mismatch (-want +got):\n%s", diff)
}
})
}
}