| // 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 |
| |
| package netstack |
| |
| import ( |
| "testing" |
| |
| "fidl/fuchsia/hardware/network" |
| "fidl/fuchsia/net/interfaces" |
| |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/sync" |
| |
| "gvisor.dev/gvisor/pkg/tcpip" |
| "gvisor.dev/gvisor/pkg/tcpip/header" |
| "gvisor.dev/gvisor/pkg/tcpip/stack" |
| ) |
| |
| var _ stack.LinkEndpoint = (*noopEndpoint)(nil) |
| |
| type noopEndpoint struct { |
| capabilities stack.LinkEndpointCapabilities |
| linkAddress tcpip.LinkAddress |
| attached chan struct{} |
| } |
| |
| func (*noopEndpoint) MTU() uint32 { |
| return header.IPv4MinimumMTU |
| } |
| |
| func (ep *noopEndpoint) Capabilities() stack.LinkEndpointCapabilities { |
| return ep.capabilities |
| } |
| |
| func (*noopEndpoint) MaxHeaderLength() uint16 { |
| return 0 |
| } |
| |
| func (ep *noopEndpoint) LinkAddress() tcpip.LinkAddress { |
| return ep.linkAddress |
| } |
| |
| func (*noopEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) { |
| return pkts.Len(), nil |
| } |
| |
| func (ep *noopEndpoint) Attach(dispatcher stack.NetworkDispatcher) { |
| if dispatcher != nil { |
| ep.attached = make(chan struct{}) |
| } else { |
| if ch := ep.attached; ch != nil { |
| close(ch) |
| } |
| ep.attached = nil |
| } |
| } |
| |
| func (ep *noopEndpoint) IsAttached() bool { |
| return ep.attached != nil |
| } |
| |
| func (ep *noopEndpoint) Wait() { |
| if ch := ep.attached; ch != nil { |
| <-ch |
| } |
| } |
| |
| func (ep *noopEndpoint) ARPHardwareType() header.ARPHardwareType { |
| return header.ARPHardwareNone |
| } |
| |
| func (*noopEndpoint) AddHeader(*stack.PacketBuffer) {} |
| |
| func (*noopEndpoint) ParseHeader(*stack.PacketBuffer) bool { return true } |
| |
| var _ link.Controller = (*noopController)(nil) |
| |
| type noopController struct { |
| onUp func() |
| } |
| |
| func (n *noopController) Up() error { |
| if fn := n.onUp; fn != nil { |
| fn() |
| } |
| return nil |
| } |
| |
| func (*noopController) Down() error { |
| return nil |
| } |
| |
| func (*noopController) SetPromiscuousMode(_ bool) error { |
| return nil |
| } |
| |
| func (*noopController) DeviceClass() network.DeviceClass { |
| return network.DeviceClassVirtual |
| } |
| |
| func (*noopController) ConnectPort(port network.PortWithCtxInterfaceRequest) { |
| _ = port.Close() |
| } |
| |
| var _ link.Observer = (*noopObserver)(nil) |
| |
| type noopObserver struct { |
| onLinkClosed func() |
| onLinkOnlineChanged func(bool) |
| } |
| |
| func (n *noopObserver) SetOnLinkClosed(fn func()) { |
| n.onLinkClosed = fn |
| } |
| |
| func (n *noopObserver) SetOnLinkOnlineChanged(fn func(bool)) { |
| n.onLinkOnlineChanged = fn |
| } |
| |
| func addLinkEndpoint(t *testing.T, ns *Netstack, name string, ep stack.LinkEndpoint) *ifState { |
| t.Helper() |
| ifs, err := ns.addEndpoint( |
| func(nicid tcpip.NICID) string { |
| prefix := t.Name() |
| for { |
| candidate := makeEndpointName(prefix, name)(nicid) |
| if overflow := len(candidate) - int(interfaces.InterfaceNameLength); overflow > 0 { |
| prefix = prefix[:len(prefix)-overflow] |
| continue |
| } |
| return candidate |
| } |
| }, |
| ep, |
| &noopController{}, |
| nil, /* observer */ |
| defaultInterfaceMetric, /* metric */ |
| qdiscConfig{}, |
| ) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return ifs |
| } |
| |
| func addNoopEndpoint(t *testing.T, ns *Netstack, name string) *ifState { |
| t.Helper() |
| return addLinkEndpoint(t, ns, name, &noopEndpoint{}) |
| } |
| |
| var _ stack.LinkEndpoint = (*sentinelEndpoint)(nil) |
| |
| type waitPair struct { |
| waitFor uint |
| ch chan struct{} |
| } |
| |
| type sentinelEndpoint struct { |
| noopEndpoint |
| mu struct { |
| sync.Mutex |
| pkts stack.PacketBufferList |
| blocking bool |
| totalEnqueued uint |
| waiters []waitPair |
| } |
| } |
| |
| func (ep *sentinelEndpoint) waitForLocked(amount uint) chan struct{} { |
| amount += uint(ep.mu.totalEnqueued) |
| ch := make(chan struct{}) |
| ep.mu.waiters = append(ep.mu.waiters, waitPair{waitFor: amount, ch: ch}) |
| ep.signalWaitersLocked() |
| return ch |
| } |
| |
| func (ep *sentinelEndpoint) WaitFor(amount uint) chan struct{} { |
| ep.mu.Lock() |
| defer ep.mu.Unlock() |
| return ep.waitForLocked(amount) |
| } |
| |
| func (ep *sentinelEndpoint) signalWaitersLocked() { |
| newWaiters := ep.mu.waiters[:0] |
| for _, waiter := range ep.mu.waiters { |
| if ep.mu.totalEnqueued >= waiter.waitFor { |
| close(waiter.ch) |
| } else { |
| newWaiters = append(newWaiters, waiter) |
| } |
| } |
| ep.mu.waiters = newWaiters |
| } |
| |
| func (ep *sentinelEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) { |
| ep.mu.Lock() |
| defer ep.mu.Unlock() |
| if ep.mu.blocking { |
| for _, pb := range pkts.AsSlice() { |
| pb.IncRef() |
| ep.mu.pkts.PushBack(pb) |
| } |
| } |
| ep.mu.totalEnqueued += uint(pkts.Len()) |
| ep.signalWaitersLocked() |
| return ep.noopEndpoint.WritePackets(pkts) |
| } |
| |
| func (ep *sentinelEndpoint) Drain() (uint, chan struct{}) { |
| ep.mu.Lock() |
| defer ep.mu.Unlock() |
| drained := ep.drainLocked() |
| return drained, ep.waitForLocked(drained) |
| } |
| |
| func (ep *sentinelEndpoint) drainLocked() uint { |
| drained := ep.mu.pkts.Len() |
| ep.mu.pkts.Reset() |
| return uint(drained) |
| } |
| |
| func (ep *sentinelEndpoint) Enqueued() uint { |
| ep.mu.Lock() |
| defer ep.mu.Unlock() |
| return ep.mu.totalEnqueued |
| } |
| |
| func (ep *sentinelEndpoint) SetBlocking(blocking bool) { |
| ep.mu.Lock() |
| defer ep.mu.Unlock() |
| ep.mu.blocking = blocking |
| if !ep.mu.blocking { |
| ep.drainLocked() |
| } |
| } |