Fix minor bugs in a couple of interface IOCTLs.

gVisor incorrectly returns the wrong ARP type for SIOGIFHWADDR. This breaks
tcpdump as it tries to interpret the packets incorrectly.

Similarly, SIOCETHTOOL is used by tcpdump to query interface properties which
fails with an EINVAL since we don't implement it. For now change it to return
EOPNOTSUPP to indicate that we don't support the query rather than return
EINVAL.

NOTE: ARPHRD types for link endpoints are distinct from NIC capabilities
and NIC flags. In Linux all 3 exist eg. ARPHRD types are stored in dev->type
field while NIC capabilities are more like the device features which can be
queried using SIOCETHTOOL but not modified and NIC Flags are fields that can
be modified from user space. eg. NIC status (UP/DOWN/MULTICAST/BROADCAST) etc.

Updates #2746

PiperOrigin-RevId: 321436525
This commit is contained in:
Bhasker Hariharan
2020-07-15 14:15:44 -07:00
committed by gVisor bot
parent 1d11c40378
commit fef90c61c6
26 changed files with 263 additions and 65 deletions
+23 -4
View File
@@ -67,10 +67,29 @@ const (
// ioctl(2) requests provided by uapi/linux/sockios.h
const (
SIOCGIFMEM = 0x891f
SIOCGIFPFLAGS = 0x8935
SIOCGMIIPHY = 0x8947
SIOCGMIIREG = 0x8948
SIOCGIFNAME = 0x8910
SIOCGIFCONF = 0x8912
SIOCGIFFLAGS = 0x8913
SIOCGIFADDR = 0x8915
SIOCGIFDSTADDR = 0x8917
SIOCGIFBRDADDR = 0x8919
SIOCGIFNETMASK = 0x891b
SIOCGIFMETRIC = 0x891d
SIOCGIFMTU = 0x8921
SIOCGIFMEM = 0x891f
SIOCGIFHWADDR = 0x8927
SIOCGIFINDEX = 0x8933
SIOCGIFPFLAGS = 0x8935
SIOCGIFTXQLEN = 0x8942
SIOCETHTOOL = 0x8946
SIOCGMIIPHY = 0x8947
SIOCGMIIREG = 0x8948
SIOCGIFMAP = 0x8970
)
// ioctl(2) requests provided by uapi/asm-generic/sockios.h
const (
SIOCGSTAMP = 0x8906
)
// ioctl(2) directions. Used to calculate requests number.
+2
View File
@@ -187,6 +187,8 @@ const (
// Device types, from uapi/linux/if_arp.h.
const (
ARPHRD_NONE = 65534
ARPHRD_ETHER = 1
ARPHRD_LOOPBACK = 772
)
+45 -29
View File
@@ -2747,7 +2747,7 @@ func (s *socketOpsCommon) ioctl(ctx context.Context, io usermem.IO, args arch.Sy
// sockets.
// TODO(b/78348848): Add a commonEndpoint method to support SIOCGSTAMP.
switch args[1].Int() {
case syscall.SIOCGSTAMP:
case linux.SIOCGSTAMP:
s.readMu.Lock()
defer s.readMu.Unlock()
if !s.timestampValid {
@@ -2788,18 +2788,19 @@ func (s *socketOpsCommon) ioctl(ctx context.Context, io usermem.IO, args arch.Sy
// Ioctl performs a socket ioctl.
func Ioctl(ctx context.Context, ep commonEndpoint, io usermem.IO, args arch.SyscallArguments) (uintptr, error) {
switch arg := int(args[1].Int()); arg {
case syscall.SIOCGIFFLAGS,
syscall.SIOCGIFADDR,
syscall.SIOCGIFBRDADDR,
syscall.SIOCGIFDSTADDR,
syscall.SIOCGIFHWADDR,
syscall.SIOCGIFINDEX,
syscall.SIOCGIFMAP,
syscall.SIOCGIFMETRIC,
syscall.SIOCGIFMTU,
syscall.SIOCGIFNAME,
syscall.SIOCGIFNETMASK,
syscall.SIOCGIFTXQLEN:
case linux.SIOCGIFFLAGS,
linux.SIOCGIFADDR,
linux.SIOCGIFBRDADDR,
linux.SIOCGIFDSTADDR,
linux.SIOCGIFHWADDR,
linux.SIOCGIFINDEX,
linux.SIOCGIFMAP,
linux.SIOCGIFMETRIC,
linux.SIOCGIFMTU,
linux.SIOCGIFNAME,
linux.SIOCGIFNETMASK,
linux.SIOCGIFTXQLEN,
linux.SIOCETHTOOL:
var ifr linux.IFReq
if _, err := usermem.CopyObjectIn(ctx, io, args[2].Pointer(), &ifr, usermem.IOOpts{
@@ -2815,7 +2816,7 @@ func Ioctl(ctx context.Context, ep commonEndpoint, io usermem.IO, args arch.Sysc
})
return 0, err
case syscall.SIOCGIFCONF:
case linux.SIOCGIFCONF:
// Return a list of interface addresses or the buffer size
// necessary to hold the list.
var ifc linux.IFConf
@@ -2889,7 +2890,7 @@ func interfaceIoctl(ctx context.Context, io usermem.IO, arg int, ifr *linux.IFRe
// SIOCGIFNAME uses ifr.ifr_ifindex rather than ifr.ifr_name to
// identify a device.
if arg == syscall.SIOCGIFNAME {
if arg == linux.SIOCGIFNAME {
// Gets the name of the interface given the interface index
// stored in ifr_ifindex.
index = int32(usermem.ByteOrder.Uint32(ifr.Data[:4]))
@@ -2912,21 +2913,28 @@ func interfaceIoctl(ctx context.Context, io usermem.IO, arg int, ifr *linux.IFRe
}
switch arg {
case syscall.SIOCGIFINDEX:
case linux.SIOCGIFINDEX:
// Copy out the index to the data.
usermem.ByteOrder.PutUint32(ifr.Data[:], uint32(index))
case syscall.SIOCGIFHWADDR:
case linux.SIOCGIFHWADDR:
// Copy the hardware address out.
ifr.Data[0] = 6 // IEEE802.2 arp type.
ifr.Data[1] = 0
//
// Refer: https://linux.die.net/man/7/netdevice
// SIOCGIFHWADDR, SIOCSIFHWADDR
//
// Get or set the hardware address of a device using
// ifr_hwaddr. The hardware address is specified in a struct
// sockaddr. sa_family contains the ARPHRD_* device type,
// sa_data the L2 hardware address starting from byte 0. Setting
// the hardware address is a privileged operation.
usermem.ByteOrder.PutUint16(ifr.Data[:], iface.DeviceType)
n := copy(ifr.Data[2:], iface.Addr)
for i := 2 + n; i < len(ifr.Data); i++ {
ifr.Data[i] = 0 // Clear padding.
}
usermem.ByteOrder.PutUint16(ifr.Data[:2], uint16(n))
case syscall.SIOCGIFFLAGS:
case linux.SIOCGIFFLAGS:
f, err := interfaceStatusFlags(stack, iface.Name)
if err != nil {
return err
@@ -2935,7 +2943,7 @@ func interfaceIoctl(ctx context.Context, io usermem.IO, arg int, ifr *linux.IFRe
// matches Linux behavior.
usermem.ByteOrder.PutUint16(ifr.Data[:2], uint16(f))
case syscall.SIOCGIFADDR:
case linux.SIOCGIFADDR:
// Copy the IPv4 address out.
for _, addr := range stack.InterfaceAddrs()[index] {
// This ioctl is only compatible with AF_INET addresses.
@@ -2946,32 +2954,32 @@ func interfaceIoctl(ctx context.Context, io usermem.IO, arg int, ifr *linux.IFRe
break
}
case syscall.SIOCGIFMETRIC:
case linux.SIOCGIFMETRIC:
// Gets the metric of the device. As per netdevice(7), this
// always just sets ifr_metric to 0.
usermem.ByteOrder.PutUint32(ifr.Data[:4], 0)
case syscall.SIOCGIFMTU:
case linux.SIOCGIFMTU:
// Gets the MTU of the device.
usermem.ByteOrder.PutUint32(ifr.Data[:4], iface.MTU)
case syscall.SIOCGIFMAP:
case linux.SIOCGIFMAP:
// Gets the hardware parameters of the device.
// TODO(gvisor.dev/issue/505): Implement.
case syscall.SIOCGIFTXQLEN:
case linux.SIOCGIFTXQLEN:
// Gets the transmit queue length of the device.
// TODO(gvisor.dev/issue/505): Implement.
case syscall.SIOCGIFDSTADDR:
case linux.SIOCGIFDSTADDR:
// Gets the destination address of a point-to-point device.
// TODO(gvisor.dev/issue/505): Implement.
case syscall.SIOCGIFBRDADDR:
case linux.SIOCGIFBRDADDR:
// Gets the broadcast address of a device.
// TODO(gvisor.dev/issue/505): Implement.
case syscall.SIOCGIFNETMASK:
case linux.SIOCGIFNETMASK:
// Gets the network mask of a device.
for _, addr := range stack.InterfaceAddrs()[index] {
// This ioctl is only compatible with AF_INET addresses.
@@ -2988,6 +2996,14 @@ func interfaceIoctl(ctx context.Context, io usermem.IO, arg int, ifr *linux.IFRe
break
}
case linux.SIOCETHTOOL:
// Stubbed out for now, Ideally we should implement the required
// sub-commands for ETHTOOL
//
// See:
// https://github.com/torvalds/linux/blob/aa0c9086b40c17a7ad94425b3b70dd1fdd7497bf/net/core/dev_ioctl.c
return syserr.ErrEndpointOperation
default:
// Not a valid call.
return syserr.ErrInvalidArgument
+17 -5
View File
@@ -15,6 +15,8 @@
package netstack
import (
"fmt"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/inet"
@@ -40,19 +42,29 @@ func (s *Stack) SupportsIPv6() bool {
return s.Stack.CheckNetworkProtocol(ipv6.ProtocolNumber)
}
// Converts Netstack's ARPHardwareType to equivalent linux constants.
func toLinuxARPHardwareType(t header.ARPHardwareType) uint16 {
switch t {
case header.ARPHardwareNone:
return linux.ARPHRD_NONE
case header.ARPHardwareLoopback:
return linux.ARPHRD_LOOPBACK
case header.ARPHardwareEther:
return linux.ARPHRD_ETHER
default:
panic(fmt.Sprintf("unknown ARPHRD type: %d", t))
}
}
// Interfaces implements inet.Stack.Interfaces.
func (s *Stack) Interfaces() map[int32]inet.Interface {
is := make(map[int32]inet.Interface)
for id, ni := range s.Stack.NICInfo() {
var devType uint16
if ni.Flags.Loopback {
devType = linux.ARPHRD_LOOPBACK
}
is[int32(id)] = inet.Interface{
Name: ni.Name,
Addr: []byte(ni.LinkAddress),
Flags: uint32(nicStateFlagsToLinux(ni.Flags)),
DeviceType: devType,
DeviceType: toLinuxARPHardwareType(ni.ARPHardwareType),
MTU: ni.MTU,
}
}
+52 -25
View File
@@ -14,14 +14,33 @@
package header
import "gvisor.dev/gvisor/pkg/tcpip"
import (
"encoding/binary"
"gvisor.dev/gvisor/pkg/tcpip"
)
const (
// ARPProtocolNumber is the ARP network protocol number.
ARPProtocolNumber tcpip.NetworkProtocolNumber = 0x0806
// ARPSize is the size of an IPv4-over-Ethernet ARP packet.
ARPSize = 2 + 2 + 1 + 1 + 2 + 2*6 + 2*4
ARPSize = 28
)
// ARPHardwareType is the hardware type for LinkEndpoint in an ARP header.
type ARPHardwareType uint16
// Typical ARP HardwareType values. Some of the constants have to be specific
// values as they are egressed on the wire in the HTYPE field of an ARP header.
const (
ARPHardwareNone ARPHardwareType = 0
// ARPHardwareEther specifically is the HTYPE for Ethernet as specified
// in the IANA list here:
//
// https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml#arp-parameters-2
ARPHardwareEther ARPHardwareType = 1
ARPHardwareLoopback ARPHardwareType = 2
)
// ARPOp is an ARP opcode.
@@ -36,54 +55,64 @@ const (
// ARP is an ARP packet stored in a byte array as described in RFC 826.
type ARP []byte
func (a ARP) hardwareAddressSpace() uint16 { return uint16(a[0])<<8 | uint16(a[1]) }
func (a ARP) protocolAddressSpace() uint16 { return uint16(a[2])<<8 | uint16(a[3]) }
func (a ARP) hardwareAddressSize() int { return int(a[4]) }
func (a ARP) protocolAddressSize() int { return int(a[5]) }
const (
hTypeOffset = 0
protocolOffset = 2
haAddressSizeOffset = 4
protoAddressSizeOffset = 5
opCodeOffset = 6
senderHAAddressOffset = 8
senderProtocolAddressOffset = senderHAAddressOffset + EthernetAddressSize
targetHAAddressOffset = senderProtocolAddressOffset + IPv4AddressSize
targetProtocolAddressOffset = targetHAAddressOffset + EthernetAddressSize
)
func (a ARP) hardwareAddressType() ARPHardwareType {
return ARPHardwareType(binary.BigEndian.Uint16(a[hTypeOffset:]))
}
func (a ARP) protocolAddressSpace() uint16 { return binary.BigEndian.Uint16(a[protocolOffset:]) }
func (a ARP) hardwareAddressSize() int { return int(a[haAddressSizeOffset]) }
func (a ARP) protocolAddressSize() int { return int(a[protoAddressSizeOffset]) }
// Op is the ARP opcode.
func (a ARP) Op() ARPOp { return ARPOp(a[6])<<8 | ARPOp(a[7]) }
func (a ARP) Op() ARPOp { return ARPOp(binary.BigEndian.Uint16(a[opCodeOffset:])) }
// SetOp sets the ARP opcode.
func (a ARP) SetOp(op ARPOp) {
a[6] = uint8(op >> 8)
a[7] = uint8(op)
binary.BigEndian.PutUint16(a[opCodeOffset:], uint16(op))
}
// SetIPv4OverEthernet configures the ARP packet for IPv4-over-Ethernet.
func (a ARP) SetIPv4OverEthernet() {
a[0], a[1] = 0, 1 // htypeEthernet
a[2], a[3] = 0x08, 0x00 // IPv4ProtocolNumber
a[4] = 6 // macSize
a[5] = uint8(IPv4AddressSize)
binary.BigEndian.PutUint16(a[hTypeOffset:], uint16(ARPHardwareEther))
binary.BigEndian.PutUint16(a[protocolOffset:], uint16(IPv4ProtocolNumber))
a[haAddressSizeOffset] = EthernetAddressSize
a[protoAddressSizeOffset] = uint8(IPv4AddressSize)
}
// HardwareAddressSender is the link address of the sender.
// It is a view on to the ARP packet so it can be used to set the value.
func (a ARP) HardwareAddressSender() []byte {
const s = 8
return a[s : s+6]
return a[senderHAAddressOffset : senderHAAddressOffset+EthernetAddressSize]
}
// ProtocolAddressSender is the protocol address of the sender.
// It is a view on to the ARP packet so it can be used to set the value.
func (a ARP) ProtocolAddressSender() []byte {
const s = 8 + 6
return a[s : s+4]
return a[senderProtocolAddressOffset : senderProtocolAddressOffset+IPv4AddressSize]
}
// HardwareAddressTarget is the link address of the target.
// It is a view on to the ARP packet so it can be used to set the value.
func (a ARP) HardwareAddressTarget() []byte {
const s = 8 + 6 + 4
return a[s : s+6]
return a[targetHAAddressOffset : targetHAAddressOffset+EthernetAddressSize]
}
// ProtocolAddressTarget is the protocol address of the target.
// It is a view on to the ARP packet so it can be used to set the value.
func (a ARP) ProtocolAddressTarget() []byte {
const s = 8 + 6 + 4 + 6
return a[s : s+4]
return a[targetProtocolAddressOffset : targetProtocolAddressOffset+IPv4AddressSize]
}
// IsValid reports whether this is an ARP packet for IPv4 over Ethernet.
@@ -91,10 +120,8 @@ func (a ARP) IsValid() bool {
if len(a) < ARPSize {
return false
}
const htypeEthernet = 1
const macSize = 6
return a.hardwareAddressSpace() == htypeEthernet &&
return a.hardwareAddressType() == ARPHardwareEther &&
a.protocolAddressSpace() == uint16(IPv4ProtocolNumber) &&
a.hardwareAddressSize() == macSize &&
a.hardwareAddressSize() == EthernetAddressSize &&
a.protocolAddressSize() == IPv4AddressSize
}
+1
View File
@@ -10,6 +10,7 @@ go_library(
"//pkg/sync",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
],
)
+6
View File
@@ -23,6 +23,7 @@ import (
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
@@ -296,3 +297,8 @@ func (e *Endpoint) AddNotify(notify Notification) *NotificationHandle {
func (e *Endpoint) RemoveNotify(handle *NotificationHandle) {
e.q.RemoveNotify(handle)
}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
func (*Endpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
+8
View File
@@ -626,6 +626,14 @@ func (e *endpoint) GSOMaxSize() uint32 {
return e.gsoMaxSize
}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
func (e *endpoint) ARPHardwareType() header.ARPHardwareType {
if e.hdrSize > 0 {
return header.ARPHardwareEther
}
return header.ARPHardwareNone
}
// InjectableEndpoint is an injectable fd-based endpoint. The endpoint writes
// to the FD, but does not read from it. All reads come from injected packets.
type InjectableEndpoint struct {
+5
View File
@@ -113,3 +113,8 @@ func (e *endpoint) WriteRawPacket(vv buffer.VectorisedView) *tcpip.Error {
return nil
}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
func (*endpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareLoopback
}
+1
View File
@@ -9,6 +9,7 @@ go_library(
deps = [
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
],
)
+6
View File
@@ -18,6 +18,7 @@ package muxed
import (
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
@@ -129,6 +130,11 @@ func (m *InjectableEndpoint) Wait() {
}
}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
func (*InjectableEndpoint) ARPHardwareType() header.ARPHardwareType {
panic("unsupported operation")
}
// NewInjectableEndpoint creates a new multi-endpoint injectable endpoint.
func NewInjectableEndpoint(routes map[tcpip.Address]stack.InjectableLinkEndpoint) *InjectableEndpoint {
return &InjectableEndpoint{
+1
View File
@@ -12,6 +12,7 @@ go_library(
"//pkg/sync",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
],
)
+6
View File
@@ -20,6 +20,7 @@ import (
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
@@ -129,3 +130,8 @@ func (e *Endpoint) GSOMaxSize() uint32 {
}
return 0
}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType
func (e *Endpoint) ARPHardwareType() header.ARPHardwareType {
return e.child.ARPHardwareType()
}
+1
View File
@@ -14,6 +14,7 @@ go_library(
"//pkg/sync",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
],
)
+6
View File
@@ -22,6 +22,7 @@ import (
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
@@ -207,3 +208,8 @@ func (e *endpoint) Wait() {
e.wg.Wait()
}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType
func (e *endpoint) ARPHardwareType() header.ARPHardwareType {
return e.lower.ARPHardwareType()
}
+5
View File
@@ -287,3 +287,8 @@ func (e *endpoint) dispatchLoop(d stack.NetworkDispatcher) {
e.completed.Done()
}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType
func (*endpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareEther
}
+10
View File
@@ -139,6 +139,7 @@ func attachOrCreateNIC(s *stack.Stack, name, prefix string, linkCaps stack.LinkE
stack: s,
nicID: id,
name: name,
isTap: prefix == "tap",
}
endpoint.Endpoint.LinkEPCapabilities = linkCaps
if endpoint.name == "" {
@@ -348,6 +349,7 @@ type tunEndpoint struct {
stack *stack.Stack
nicID tcpip.NICID
name string
isTap bool
}
// DecRef decrements refcount of e, removes NIC if refcount goes to 0.
@@ -356,3 +358,11 @@ func (e *tunEndpoint) DecRef() {
e.stack.RemoveNIC(e.nicID)
})
}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
func (e *tunEndpoint) ARPHardwareType() header.ARPHardwareType {
if e.isTap {
return header.ARPHardwareEther
}
return header.ARPHardwareNone
}
+2
View File
@@ -12,6 +12,7 @@ go_library(
"//pkg/gate",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
],
)
@@ -25,6 +26,7 @@ go_test(
deps = [
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
],
)
+6
View File
@@ -25,6 +25,7 @@ import (
"gvisor.dev/gvisor/pkg/gate"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
@@ -147,3 +148,8 @@ func (e *Endpoint) WaitDispatch() {
// Wait implements stack.LinkEndpoint.Wait.
func (e *Endpoint) Wait() {}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
func (e *Endpoint) ARPHardwareType() header.ARPHardwareType {
return e.lower.ARPHardwareType()
}
+6
View File
@@ -19,6 +19,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
@@ -81,6 +82,11 @@ func (e *countedEndpoint) WriteRawPacket(buffer.VectorisedView) *tcpip.Error {
return nil
}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
func (*countedEndpoint) ARPHardwareType() header.ARPHardwareType {
panic("unimplemented")
}
// Wait implements stack.LinkEndpoint.Wait.
func (*countedEndpoint) Wait() {}

Some files were not shown because too many files have changed in this diff Show More