2021-10-05 15:39:50 -07:00
|
|
|
// Copyright 2021 The gVisor Authors.
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
|
|
//go:build linux
|
|
|
|
|
// +build linux
|
|
|
|
|
|
|
|
|
|
package sharedmem
|
|
|
|
|
|
|
|
|
|
import (
|
2022-04-25 20:38:37 -07:00
|
|
|
"gvisor.dev/gvisor/pkg/atomicbitops"
|
2023-06-01 21:25:01 -07:00
|
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
2024-06-29 05:08:21 -07:00
|
|
|
"gvisor.dev/gvisor/pkg/rawfile"
|
2021-10-05 15:39:50 -07:00
|
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
|
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
|
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
|
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
|
|
|
)
|
|
|
|
|
|
2024-08-16 10:43:33 -07:00
|
|
|
// +stateify savable
|
2021-10-05 15:39:50 -07:00
|
|
|
type serverEndpoint struct {
|
|
|
|
|
// bufferSize is the size of each individual buffer.
|
|
|
|
|
// bufferSize is immutable.
|
|
|
|
|
bufferSize uint32
|
|
|
|
|
|
|
|
|
|
// rx is the receive queue.
|
|
|
|
|
rx serverRx
|
|
|
|
|
|
2022-04-25 20:38:37 -07:00
|
|
|
// stopRequested determines whether the worker goroutines should stop.
|
|
|
|
|
stopRequested atomicbitops.Uint32
|
2021-10-05 15:39:50 -07:00
|
|
|
|
|
|
|
|
// Wait group used to indicate that all workers have stopped.
|
2024-08-16 10:43:33 -07:00
|
|
|
completed sync.WaitGroup `state:"nosave"`
|
2021-10-05 15:39:50 -07:00
|
|
|
|
|
|
|
|
// peerFD is an fd to the peer that can be used to detect when the peer is
|
|
|
|
|
// gone.
|
|
|
|
|
// peerFD is immutable.
|
|
|
|
|
peerFD int
|
|
|
|
|
|
|
|
|
|
// caps holds the endpoint capabilities.
|
|
|
|
|
caps stack.LinkEndpointCapabilities
|
|
|
|
|
|
|
|
|
|
// hdrSize is the size of the link layer header if any.
|
|
|
|
|
// hdrSize is immutable.
|
|
|
|
|
hdrSize uint32
|
|
|
|
|
|
2021-12-13 23:36:02 -08:00
|
|
|
// virtioNetHeaderRequired if true indicates that a virtio header is expected
|
|
|
|
|
// in all inbound/outbound packets.
|
|
|
|
|
virtioNetHeaderRequired bool
|
|
|
|
|
|
2021-10-05 15:39:50 -07:00
|
|
|
// onClosed is a function to be called when the FD's peer (if any) closes its
|
|
|
|
|
// end of the communication pipe.
|
2024-08-16 10:43:33 -07:00
|
|
|
onClosed func(tcpip.Error) `state:"nosave"`
|
2021-10-05 15:39:50 -07:00
|
|
|
|
|
|
|
|
// mu protects the following fields.
|
2024-10-31 18:53:45 -07:00
|
|
|
mu serverEndpointRWMutex `state:"nosave"`
|
2021-10-05 15:39:50 -07:00
|
|
|
|
|
|
|
|
// tx is the transmit queue.
|
|
|
|
|
// +checklocks:mu
|
|
|
|
|
tx serverTx
|
|
|
|
|
|
|
|
|
|
// workerStarted specifies whether the worker goroutine was started.
|
|
|
|
|
// +checklocks:mu
|
|
|
|
|
workerStarted bool
|
2024-06-21 17:13:26 -07:00
|
|
|
|
|
|
|
|
// addr is the local address of this endpoint.
|
|
|
|
|
//
|
|
|
|
|
// +checklocks:mu
|
|
|
|
|
addr tcpip.LinkAddress
|
2024-06-24 16:54:47 -07:00
|
|
|
// mtu (maximum transmission unit) is the maximum size of a packet.
|
|
|
|
|
// +checklocks:mu
|
|
|
|
|
mtu uint32
|
2021-10-05 15:39:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewServerEndpoint creates a new shared-memory-based endpoint. Buffers will be
|
|
|
|
|
// broken up into buffers of "bufferSize" bytes.
|
|
|
|
|
func NewServerEndpoint(opts Options) (stack.LinkEndpoint, error) {
|
|
|
|
|
e := &serverEndpoint{
|
|
|
|
|
mtu: opts.MTU,
|
|
|
|
|
bufferSize: opts.BufferSize,
|
|
|
|
|
addr: opts.LinkAddress,
|
|
|
|
|
peerFD: opts.PeerFD,
|
|
|
|
|
onClosed: opts.OnClosed,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := e.tx.init(&opts.RX); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := e.rx.init(&opts.TX); err != nil {
|
|
|
|
|
e.tx.cleanup()
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.caps = stack.LinkEndpointCapabilities(0)
|
|
|
|
|
if opts.RXChecksumOffload {
|
|
|
|
|
e.caps |= stack.CapabilityRXChecksumOffload
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if opts.TXChecksumOffload {
|
|
|
|
|
e.caps |= stack.CapabilityTXChecksumOffload
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if opts.LinkAddress != "" {
|
|
|
|
|
e.hdrSize = header.EthernetMinimumSize
|
|
|
|
|
e.caps |= stack.CapabilityResolutionRequired
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return e, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-02 16:45:52 -07:00
|
|
|
// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction.
|
|
|
|
|
func (*serverEndpoint) SetOnCloseAction(func()) {}
|
|
|
|
|
|
2021-10-05 15:39:50 -07:00
|
|
|
// Close frees all resources associated with the endpoint.
|
|
|
|
|
func (e *serverEndpoint) Close() {
|
|
|
|
|
// Tell dispatch goroutine to stop, then write to the eventfd so that it wakes
|
|
|
|
|
// up in case it's sleeping.
|
2022-04-25 20:38:37 -07:00
|
|
|
e.stopRequested.Store(1)
|
2021-10-07 17:37:50 -07:00
|
|
|
e.rx.eventFD.Notify()
|
2021-10-05 15:39:50 -07:00
|
|
|
|
|
|
|
|
// Cleanup the queues inline if the worker hasn't started yet; we also know it
|
|
|
|
|
// won't start from now on because stopRequested is set to 1.
|
|
|
|
|
e.mu.Lock()
|
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
|
workerPresent := e.workerStarted
|
|
|
|
|
|
|
|
|
|
if !workerPresent {
|
|
|
|
|
e.tx.cleanup()
|
|
|
|
|
e.rx.cleanup()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wait implements stack.LinkEndpoint.Wait. It waits until all workers have
|
|
|
|
|
// stopped after a Close() call.
|
|
|
|
|
func (e *serverEndpoint) Wait() {
|
|
|
|
|
e.completed.Wait()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attach implements stack.LinkEndpoint.Attach. It launches the goroutine that
|
|
|
|
|
// reads packets from the rx queue.
|
|
|
|
|
func (e *serverEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
|
|
|
|
e.mu.Lock()
|
2022-04-25 20:38:37 -07:00
|
|
|
if !e.workerStarted && e.stopRequested.Load() == 0 {
|
2021-10-05 15:39:50 -07:00
|
|
|
e.workerStarted = true
|
|
|
|
|
e.completed.Add(1)
|
|
|
|
|
if e.peerFD >= 0 {
|
|
|
|
|
e.completed.Add(1)
|
|
|
|
|
// Spin up a goroutine to monitor for peer shutdown.
|
|
|
|
|
go func() {
|
|
|
|
|
b := make([]byte, 1)
|
|
|
|
|
// When sharedmem endpoint is in use the peerFD is never used for any
|
|
|
|
|
// data transfer and this Read should only return if the peer is
|
|
|
|
|
// shutting down.
|
2024-06-29 05:08:21 -07:00
|
|
|
_, errno := rawfile.BlockingRead(e.peerFD, b)
|
2021-10-05 15:39:50 -07:00
|
|
|
if e.onClosed != nil {
|
2024-06-29 05:08:21 -07:00
|
|
|
if errno == 0 {
|
|
|
|
|
e.onClosed(nil)
|
|
|
|
|
} else {
|
|
|
|
|
e.onClosed(tcpip.TranslateErrno(errno))
|
|
|
|
|
}
|
2021-10-05 15:39:50 -07:00
|
|
|
}
|
|
|
|
|
e.completed.Done()
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
// Link endpoints are not savable. When transportation endpoints are saved,
|
|
|
|
|
// they stop sending outgoing packets and all incoming packets are rejected.
|
|
|
|
|
go e.dispatchLoop(dispatcher) // S/R-SAFE: see above.
|
|
|
|
|
}
|
|
|
|
|
e.mu.Unlock()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IsAttached implements stack.LinkEndpoint.IsAttached.
|
|
|
|
|
func (e *serverEndpoint) IsAttached() bool {
|
|
|
|
|
e.mu.Lock()
|
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
|
return e.workerStarted
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 16:54:47 -07:00
|
|
|
// MTU implements stack.LinkEndpoint.MTU.
|
2021-10-05 15:39:50 -07:00
|
|
|
func (e *serverEndpoint) MTU() uint32 {
|
2024-06-24 16:54:47 -07:00
|
|
|
e.mu.RLock()
|
|
|
|
|
defer e.mu.RUnlock()
|
2022-02-25 11:08:46 -08:00
|
|
|
return e.mtu
|
2021-10-05 15:39:50 -07:00
|
|
|
}
|
|
|
|
|
|
2024-06-24 16:54:47 -07:00
|
|
|
func (e *serverEndpoint) SetMTU(mtu uint32) {
|
|
|
|
|
e.mu.Lock()
|
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
|
e.mtu = mtu
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-05 15:39:50 -07:00
|
|
|
// Capabilities implements stack.LinkEndpoint.Capabilities.
|
|
|
|
|
func (e *serverEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
|
|
|
|
return e.caps
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MaxHeaderLength implements stack.LinkEndpoint.MaxHeaderLength. It returns the
|
|
|
|
|
// ethernet frame header size.
|
|
|
|
|
func (e *serverEndpoint) MaxHeaderLength() uint16 {
|
|
|
|
|
return uint16(e.hdrSize)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LinkAddress implements stack.LinkEndpoint.LinkAddress. It returns the local
|
|
|
|
|
// link address.
|
|
|
|
|
func (e *serverEndpoint) LinkAddress() tcpip.LinkAddress {
|
2024-06-21 17:13:26 -07:00
|
|
|
e.mu.RLock()
|
|
|
|
|
defer e.mu.RUnlock()
|
2021-10-05 15:39:50 -07:00
|
|
|
return e.addr
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-11 21:37:10 -07:00
|
|
|
// SetLinkAddress implements stack.LinkEndpoint.SetLinkAddress.
|
|
|
|
|
func (e *serverEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
|
|
|
|
e.mu.Lock()
|
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
|
e.addr = addr
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-05 15:39:50 -07:00
|
|
|
// AddHeader implements stack.LinkEndpoint.AddHeader.
|
2024-02-29 11:07:59 -08:00
|
|
|
func (e *serverEndpoint) AddHeader(pkt *stack.PacketBuffer) {
|
2024-06-21 17:13:26 -07:00
|
|
|
e.mu.RLock()
|
|
|
|
|
defer e.mu.RUnlock()
|
2021-10-05 15:39:50 -07:00
|
|
|
// Add ethernet header if needed.
|
2022-01-26 14:27:06 -08:00
|
|
|
if len(e.addr) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-05 15:39:50 -07:00
|
|
|
eth := header.Ethernet(pkt.LinkHeader().Push(header.EthernetMinimumSize))
|
2022-01-26 16:02:39 -08:00
|
|
|
eth.Encode(&header.EthernetFields{
|
|
|
|
|
SrcAddr: pkt.EgressRoute.LocalLinkAddress,
|
|
|
|
|
DstAddr: pkt.EgressRoute.RemoteLinkAddress,
|
|
|
|
|
Type: pkt.NetworkProtocolNumber,
|
|
|
|
|
})
|
2021-10-05 15:39:50 -07:00
|
|
|
}
|
|
|
|
|
|
2024-02-29 11:07:59 -08:00
|
|
|
func (e *serverEndpoint) parseHeader(pkt *stack.PacketBuffer) bool {
|
2023-06-21 13:38:29 -07:00
|
|
|
_, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize)
|
|
|
|
|
return ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ParseHeader implements stack.LinkEndpoint.ParseHeader.
|
2024-02-29 11:07:59 -08:00
|
|
|
func (e *serverEndpoint) ParseHeader(pkt *stack.PacketBuffer) bool {
|
2024-06-21 17:13:26 -07:00
|
|
|
e.mu.RLock()
|
|
|
|
|
defer e.mu.RUnlock()
|
2023-06-21 13:38:29 -07:00
|
|
|
// Add ethernet header if needed.
|
|
|
|
|
if len(e.addr) == 0 {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return e.parseHeader(pkt)
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-29 11:07:59 -08:00
|
|
|
func (e *serverEndpoint) AddVirtioNetHeader(pkt *stack.PacketBuffer) {
|
2021-12-13 23:36:02 -08:00
|
|
|
virtio := header.VirtioNetHeader(pkt.VirtioNetHeader().Push(header.VirtioNetHeaderSize))
|
|
|
|
|
virtio.Encode(&header.VirtioNetHeaderFields{})
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-05 15:39:50 -07:00
|
|
|
// +checklocks:e.mu
|
2024-02-29 11:07:59 -08:00
|
|
|
func (e *serverEndpoint) writePacketLocked(r stack.RouteInfo, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
|
2021-12-13 23:36:02 -08:00
|
|
|
if e.virtioNetHeaderRequired {
|
|
|
|
|
e.AddVirtioNetHeader(pkt)
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-30 18:38:31 -07:00
|
|
|
ok := e.tx.transmit(pkt)
|
2021-10-05 15:39:50 -07:00
|
|
|
if !ok {
|
|
|
|
|
return &tcpip.ErrWouldBlock{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WritePacket writes outbound packets to the file descriptor. If it is not
|
|
|
|
|
// currently writable, the packet is dropped.
|
2021-10-13 15:17:23 -07:00
|
|
|
// WritePacket implements stack.LinkEndpoint.WritePacket.
|
2024-02-29 11:07:59 -08:00
|
|
|
func (e *serverEndpoint) WritePacket(_ stack.RouteInfo, _ tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
|
2021-10-05 15:39:50 -07:00
|
|
|
// Transmit the packet.
|
|
|
|
|
e.mu.Lock()
|
|
|
|
|
defer e.mu.Unlock()
|
2021-10-13 15:17:23 -07:00
|
|
|
if err := e.writePacketLocked(pkt.EgressRoute, pkt.NetworkProtocolNumber, pkt); err != nil {
|
2021-10-05 15:39:50 -07:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
e.tx.notify()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WritePackets implements stack.LinkEndpoint.WritePackets.
|
2022-01-14 13:20:15 -08:00
|
|
|
func (e *serverEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
2021-10-05 15:39:50 -07:00
|
|
|
n := 0
|
|
|
|
|
var err tcpip.Error
|
|
|
|
|
e.mu.Lock()
|
|
|
|
|
defer e.mu.Unlock()
|
2023-05-17 12:06:30 -07:00
|
|
|
for _, pkt := range pkts.AsSlice() {
|
2021-10-13 15:17:23 -07:00
|
|
|
if err = e.writePacketLocked(pkt.EgressRoute, pkt.NetworkProtocolNumber, pkt); err != nil {
|
2021-10-05 15:39:50 -07:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
n++
|
|
|
|
|
}
|
|
|
|
|
// WritePackets never returns an error if it successfully transmitted at least
|
|
|
|
|
// one packet.
|
|
|
|
|
if err != nil && n == 0 {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
e.tx.notify()
|
|
|
|
|
return n, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dispatchLoop reads packets from the rx queue in a loop and dispatches them
|
|
|
|
|
// to the network stack.
|
|
|
|
|
func (e *serverEndpoint) dispatchLoop(d stack.NetworkDispatcher) {
|
2022-04-25 20:38:37 -07:00
|
|
|
for e.stopRequested.Load() == 0 {
|
2021-10-05 15:39:50 -07:00
|
|
|
b := e.rx.receive()
|
|
|
|
|
if b == nil {
|
2021-10-27 17:22:48 -07:00
|
|
|
e.rx.EnableNotification()
|
|
|
|
|
// Now pull again to make sure we didn't receive any packets
|
|
|
|
|
// while notifications were not enabled.
|
|
|
|
|
for {
|
|
|
|
|
b = e.rx.receive()
|
|
|
|
|
if b != nil {
|
|
|
|
|
// Disable notifications as we only need to be notified when we are going
|
|
|
|
|
// to block on eventFD. This should prevent the peer from needlessly
|
|
|
|
|
// writing to eventFD when this end is already awake and processing
|
|
|
|
|
// packets.
|
|
|
|
|
e.rx.DisableNotification()
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
e.rx.waitForPackets()
|
|
|
|
|
}
|
2021-10-05 15:39:50 -07:00
|
|
|
}
|
|
|
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
2023-06-01 21:25:01 -07:00
|
|
|
Payload: buffer.MakeWithView(b),
|
2021-10-05 15:39:50 -07:00
|
|
|
})
|
2021-12-13 23:36:02 -08:00
|
|
|
if e.virtioNetHeaderRequired {
|
|
|
|
|
_, ok := pkt.VirtioNetHeader().Consume(header.VirtioNetHeaderSize)
|
|
|
|
|
if !ok {
|
|
|
|
|
pkt.DecRef()
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-05 15:39:50 -07:00
|
|
|
var proto tcpip.NetworkProtocolNumber
|
2024-06-21 17:13:26 -07:00
|
|
|
e.mu.RLock()
|
|
|
|
|
addrLen := len(e.addr)
|
|
|
|
|
e.mu.RUnlock()
|
|
|
|
|
if addrLen != 0 {
|
2023-06-21 13:38:29 -07:00
|
|
|
if !e.parseHeader(pkt) {
|
2021-11-08 13:26:02 -08:00
|
|
|
pkt.DecRef()
|
2021-10-05 15:39:50 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2023-06-21 13:38:29 -07:00
|
|
|
proto = header.Ethernet(pkt.LinkHeader().Slice()).Type()
|
2021-10-05 15:39:50 -07:00
|
|
|
} else {
|
|
|
|
|
// We don't get any indication of what the packet is, so try to guess
|
|
|
|
|
// if it's an IPv4 or IPv6 packet.
|
|
|
|
|
// IP version information is at the first octet, so pulling up 1 byte.
|
|
|
|
|
h, ok := pkt.Data().PullUp(1)
|
|
|
|
|
if !ok {
|
2021-11-08 13:26:02 -08:00
|
|
|
pkt.DecRef()
|
2021-10-05 15:39:50 -07:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
switch header.IPVersion(h) {
|
|
|
|
|
case header.IPv4Version:
|
|
|
|
|
proto = header.IPv4ProtocolNumber
|
|
|
|
|
case header.IPv6Version:
|
|
|
|
|
proto = header.IPv6ProtocolNumber
|
|
|
|
|
default:
|
2021-11-08 13:26:02 -08:00
|
|
|
pkt.DecRef()
|
2021-10-05 15:39:50 -07:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Send packet up the stack.
|
2022-01-26 13:21:16 -08:00
|
|
|
d.DeliverNetworkPacket(proto, pkt)
|
2021-11-08 13:26:02 -08:00
|
|
|
pkt.DecRef()
|
2021-10-05 15:39:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.mu.Lock()
|
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
// Clean state.
|
|
|
|
|
e.tx.cleanup()
|
|
|
|
|
e.rx.cleanup()
|
|
|
|
|
|
|
|
|
|
e.completed.Done()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType
|
|
|
|
|
func (e *serverEndpoint) ARPHardwareType() header.ARPHardwareType {
|
|
|
|
|
if e.hdrSize > 0 {
|
|
|
|
|
return header.ARPHardwareEther
|
|
|
|
|
}
|
|
|
|
|
return header.ARPHardwareNone
|
|
|
|
|
}
|