func (e *endpoint) Bind(addr tcpip.FullAddress, commit func() *tcpip.Error) *tcpip.Error {
	e.mu.Lock()
	defer e.mu.Unlock()
	err := e.bindLocked(addr, commit)
	if err != nil {
		return err
	}
	e.bindNICID = addr.NIC
	e.bindAddr = addr.Addr
	return nil
}
Bind binds the endpoint to a specific local address and port.
Specifying a NIC is optional.
package ping
import (
	"encoding/binary"
	"sync"
	"github.com/brewlin/net-protocol/pkg/sleep"
	tcpip "github.com/brewlin/net-protocol/protocol"
	"github.com/brewlin/net-protocol/pkg/buffer"
	"github.com/brewlin/net-protocol/protocol/header"
	"github.com/brewlin/net-protocol/stack"
	"github.com/brewlin/net-protocol/pkg/waiter"
)
type pingPacket struct {
	pingPacketEntry
	senderAddress tcpip.FullAddress
	data          buffer.VectorisedView
	timestamp     int64
	hasTimestamp  bool
	views [8]buffer.View
}
type endpointState int
const (
	stateInitial endpointState = iota
	stateBound
	stateConnected
	stateClosed
)
type endpoint struct {
	stack       *stack.Stack
	netProto    tcpip.NetworkProtocolNumber
	transProto  tcpip.TransportProtocolNumber
	waiterQueue *waiter.Queue
	rcvMu         sync.Mutex
	rcvReady      bool
	rcvList       pingPacketList
	rcvBufSizeMax int
	rcvBufSize    int
	rcvClosed     bool
	rcvTimestamp  bool
	mu         sync.RWMutex
	sndBufSize int
	shutdownFlags tcpip.ShutdownFlags
	id            stack.TransportEndpointID
	state         endpointState
	bindNICID     tcpip.NICID
	bindAddr      tcpip.Address
	regNICID      tcpip.NICID
	route         stack.Route
}
func newEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber,
	transProto tcpip.TransportProtocolNumber, waiterQueue *waiter.Queue) *endpoint {
	return &endpoint{
		stack:         stack,
		netProto:      netProto,
		transProto:    transProto,
		waiterQueue:   waiterQueue,
		rcvBufSizeMax: 32 * 1024,
		sndBufSize:    32 * 1024,
	}
}
func (e *endpoint) Close() {
	e.mu.Lock()
	e.shutdownFlags = tcpip.ShutdownRead | tcpip.ShutdownWrite
	switch e.state {
	case stateBound, stateConnected:
		e.stack.UnregisterTransportEndpoint(e.regNICID, []tcpip.NetworkProtocolNumber{e.netProto}, e.transProto, e.id)
	}
	e.rcvMu.Lock()
	e.rcvClosed = true
	e.rcvBufSize = 0
	for !e.rcvList.Empty() {
		p := e.rcvList.Front()
		e.rcvList.Remove(p)
	}
	e.rcvMu.Unlock()
	e.route.Release()
	e.state = stateClosed
	e.mu.Unlock()
	e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.EventIn | waiter.EventOut)
}
func (e *endpoint) Read(addr *tcpip.FullAddress) (buffer.View, tcpip.ControlMessages, *tcpip.Error) {
	e.rcvMu.Lock()
	if e.rcvList.Empty() {
		err := tcpip.ErrWouldBlock
		if e.rcvClosed {
			err = tcpip.ErrClosedForReceive
		}
		e.rcvMu.Unlock()
		return buffer.View{}, tcpip.ControlMessages{}, err
	}
	p := e.rcvList.Front()
	e.rcvList.Remove(p)
	e.rcvBufSize -= p.data.Size()
	ts := e.rcvTimestamp
	e.rcvMu.Unlock()
	if addr != nil {
		*addr = p.senderAddress
	}
	if ts && !p.hasTimestamp {
		p.timestamp = e.stack.NowNanoseconds()
	}
	return p.data.ToView(), tcpip.ControlMessages{HasTimestamp: ts, Timestamp: p.timestamp}, nil
}
func (e *endpoint) prepareForWrite(to *tcpip.FullAddress) (retry bool, err *tcpip.Error) {
	switch e.state {
	case stateInitial:
	case stateConnected:
		return false, nil
	case stateBound:
		if to == nil {
			return false, tcpip.ErrDestinationRequired
		}
		return false, nil
	default:
		return false, tcpip.ErrInvalidEndpointState
	}
	e.mu.RUnlock()
	defer e.mu.RLock()
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.state != stateInitial {
		return true, nil
	}
	if err := e.bindLocked(tcpip.FullAddress{}, nil); err != nil {
		return false, err
	}
	return true, nil
}
func (e *endpoint) Write(p tcpip.Payload, opts tcpip.WriteOptions) (uintptr, <-chan struct{}, *tcpip.Error) {
	if opts.More {
		return 0, nil, tcpip.ErrInvalidOptionValue
	}
	to := opts.To
	e.mu.RLock()
	defer e.mu.RUnlock()
	if e.shutdownFlags&tcpip.ShutdownWrite != 0 {
		return 0, nil, tcpip.ErrClosedForSend
	}
	for {
		retry, err := e.prepareForWrite(to)
		if err != nil {
			return 0, nil, err
		}
		if !retry {
			break
		}
	}
	var route *stack.Route
	if to == nil {
		route = &e.route
		if route.IsResolutionRequired() {
			e.mu.RUnlock()
			defer e.mu.RLock()
			e.mu.Lock()
			defer e.mu.Unlock()
			if e.state != stateConnected {
				return 0, nil, tcpip.ErrInvalidEndpointState
			}
		}
	} else {
		nicid := to.NIC
		if e.bindNICID != 0 {
			if nicid != 0 && nicid != e.bindNICID {
				return 0, nil, tcpip.ErrNoRoute
			}
			nicid = e.bindNICID
		}
		toCopy := *to
		to = &toCopy
		netProto, err := e.checkV4Mapped(to, true)
		if err != nil {
			return 0, nil, err
		}
		r, err := e.stack.FindRoute(nicid, e.bindAddr, to.Addr, netProto)
		if err != nil {
			return 0, nil, err
		}
		defer r.Release()
		route = &r
	}
	if route.IsResolutionRequired() {
		waker := &sleep.Waker{}
		if ch, err := route.Resolve(waker); err != nil {
			if err == tcpip.ErrWouldBlock {
				route.RemoveWaker(waker)
				return 0, ch, tcpip.ErrNoLinkAddress
			}
			return 0, nil, err
		}
	}
	v, err := p.Get(p.Size())
	if err != nil {
		return 0, nil, err
	}
	switch e.netProto {
	case header.IPv4ProtocolNumber:
		err = sendPing4(route, e.id.LocalPort, v)
	case header.IPv6ProtocolNumber:
		err = sendPing6(route, e.id.LocalPort, v)
	}
	return uintptr(len(v)), nil, err
}
func (e *endpoint) Peek([][]byte) (uintptr, tcpip.ControlMessages, *tcpip.Error) {
	return 0, tcpip.ControlMessages{}, nil
}
func (e *endpoint) SetSockOpt(opt interface{}) *tcpip.Error {
	switch v := opt.(type) {
	case tcpip.TimestampOption:
		e.rcvMu.Lock()
		e.rcvTimestamp = v != 0
		e.rcvMu.Unlock()
	}
	return nil
}
func (e *endpoint) GetSockOpt(opt interface{}) *tcpip.Error {
	switch o := opt.(type) {
	case tcpip.ErrorOption:
		return nil
	case *tcpip.SendBufferSizeOption:
		e.mu.Lock()
		*o = tcpip.SendBufferSizeOption(e.sndBufSize)
		e.mu.Unlock()
		return nil
	case *tcpip.ReceiveBufferSizeOption:
		e.rcvMu.Lock()
		*o = tcpip.ReceiveBufferSizeOption(e.rcvBufSizeMax)
		e.rcvMu.Unlock()
		return nil
	case *tcpip.ReceiveQueueSizeOption:
		e.rcvMu.Lock()
		if e.rcvList.Empty() {
			*o = 0
		} else {
			p := e.rcvList.Front()
			*o = tcpip.ReceiveQueueSizeOption(p.data.Size())
		}
		e.rcvMu.Unlock()
		return nil
	case *tcpip.TimestampOption:
		e.rcvMu.Lock()
		*o = 0
		if e.rcvTimestamp {
			*o = 1
		}
		e.rcvMu.Unlock()
	}
	return tcpip.ErrUnknownProtocolOption
}
func sendPing4(r *stack.Route, ident uint16, data buffer.View) *tcpip.Error {
	if len(data) < header.ICMPv4EchoMinimumSize {
		return tcpip.ErrInvalidEndpointState
	}
	binary.BigEndian.PutUint16(data[header.ICMPv4MinimumSize:], ident)
	hdr := buffer.NewPrependable(header.ICMPv4EchoMinimumSize + int(r.MaxHeaderLength()))
	icmpv4 := header.ICMPv4(hdr.Prepend(header.ICMPv4EchoMinimumSize))
	copy(icmpv4, data)
	data = data[header.ICMPv4EchoMinimumSize:]
	if icmpv4.Type() != header.ICMPv4Echo || icmpv4.Code() != 0 {
		return tcpip.ErrInvalidEndpointState
	}
	icmpv4.SetChecksum(0)
	icmpv4.SetChecksum(^header.Checksum(icmpv4, header.Checksum(data, 0)))
	return r.WritePacket(hdr, data.ToVectorisedView(), header.ICMPv4ProtocolNumber, r.DefaultTTL())
}
func sendPing6(r *stack.Route, ident uint16, data buffer.View) *tcpip.Error {
	if len(data) < header.ICMPv6EchoMinimumSize {
		return tcpip.ErrInvalidEndpointState
	}
	binary.BigEndian.PutUint16(data[header.ICMPv6MinimumSize:], ident)
	hdr := buffer.NewPrependable(header.ICMPv6EchoMinimumSize + int(r.MaxHeaderLength()))
	icmpv6 := header.ICMPv6(hdr.Prepend(header.ICMPv6EchoMinimumSize))
	copy(icmpv6, data)
	data = data[header.ICMPv6EchoMinimumSize:]
	if icmpv6.Type() != header.ICMPv6EchoRequest || icmpv6.Code() != 0 {
		return tcpip.ErrInvalidEndpointState
	}
	icmpv6.SetChecksum(0)
	icmpv6.SetChecksum(^header.Checksum(icmpv6, header.Checksum(data, 0)))
	return r.WritePacket(hdr, data.ToVectorisedView(), header.ICMPv6ProtocolNumber, r.DefaultTTL())
}
func (e *endpoint) checkV4Mapped(addr *tcpip.FullAddress, allowMismatch bool) (tcpip.NetworkProtocolNumber, *tcpip.Error) {
	netProto := e.netProto
	if header.IsV4MappedAddress(addr.Addr) {
		return 0, tcpip.ErrNoRoute
	}
	if l := len(e.id.LocalAddress); !allowMismatch && l != 0 && l != len(addr.Addr) {
		return 0, tcpip.ErrInvalidEndpointState
	}
	return netProto, nil
}
func (e *endpoint) Connect(addr tcpip.FullAddress) *tcpip.Error {
	e.mu.Lock()
	defer e.mu.Unlock()
	nicid := addr.NIC
	localPort := uint16(0)
	switch e.state {
	case stateBound, stateConnected:
		localPort = e.id.LocalPort
		if e.bindNICID == 0 {
			break
		}
		if nicid != 0 && nicid != e.bindNICID {
			return tcpip.ErrInvalidEndpointState
		}
		nicid = e.bindNICID
	default:
		return tcpip.ErrInvalidEndpointState
	}
	netProto, err := e.checkV4Mapped(&addr, false)
	if err != nil {
		return err
	}
	r, err := e.stack.FindRoute(nicid, e.bindAddr, addr.Addr, netProto)
	if err != nil {
		return err
	}
	defer r.Release()
	id := stack.TransportEndpointID{
		LocalAddress:  r.LocalAddress,
		LocalPort:     localPort,
		RemoteAddress: r.RemoteAddress,
	}
	netProtos := []tcpip.NetworkProtocolNumber{netProto}
	id, err = e.registerWithStack(nicid, netProtos, id)
	if err != nil {
		return err
	}
	e.id = id
	e.route = r.Clone()
	e.regNICID = nicid
	e.state = stateConnected
	e.rcvMu.Lock()
	e.rcvReady = true
	e.rcvMu.Unlock()
	return nil
}
func (*endpoint) ConnectEndpoint(tcpip.Endpoint) *tcpip.Error {
	return tcpip.ErrInvalidEndpointState
}
func (e *endpoint) Shutdown(flags tcpip.ShutdownFlags) *tcpip.Error {
	e.mu.Lock()
	defer e.mu.Unlock()
	e.shutdownFlags |= flags
	if e.state != stateConnected {
		return tcpip.ErrNotConnected
	}
	if flags&tcpip.ShutdownRead != 0 {
		e.rcvMu.Lock()
		wasClosed := e.rcvClosed
		e.rcvClosed = true
		e.rcvMu.Unlock()
		if !wasClosed {
			e.waiterQueue.Notify(waiter.EventIn)
		}
	}
	return nil
}
func (*endpoint) Listen(int) *tcpip.Error {
	return tcpip.ErrNotSupported
}
func (*endpoint) Accept() (tcpip.Endpoint, *waiter.Queue, *tcpip.Error) {
	return nil, nil, tcpip.ErrNotSupported
}
func (e *endpoint) registerWithStack(nicid tcpip.NICID, netProtos []tcpip.NetworkProtocolNumber,
	id stack.TransportEndpointID) (stack.TransportEndpointID, *tcpip.Error) {
	if id.LocalPort != 0 {
		err := e.stack.RegisterTransportEndpoint(nicid, netProtos, e.transProto, id, e)
		return id, err
	}
	_, err := e.stack.PickEphemeralPort(func(p uint16) (bool, *tcpip.Error) {
		id.LocalPort = p
		err := e.stack.RegisterTransportEndpoint(nicid, netProtos, e.transProto, id, e)
		switch err {
		case nil:
			return true, nil
		case tcpip.ErrPortInUse:
			return false, nil
		default:
			return false, err
		}
	})
	return id, err
}
func (e *endpoint) bindLocked(addr tcpip.FullAddress, commit func() *tcpip.Error) *tcpip.Error {
	if e.state != stateInitial {
		return tcpip.ErrInvalidEndpointState
	}
	netProto, err := e.checkV4Mapped(&addr, false)
	if err != nil {
		return err
	}
	netProtos := []tcpip.NetworkProtocolNumber{netProto}
	if len(addr.Addr) != 0 {
		if e.stack.CheckLocalAddress(addr.NIC, netProto, addr.Addr) == 0 {
			return tcpip.ErrBadLocalAddress
		}
	}
	id := stack.TransportEndpointID{
		LocalPort:    addr.Port,
		LocalAddress: addr.Addr,
	}
	id, err = e.registerWithStack(addr.NIC, netProtos, id)
	if err != nil {
		return err
	}
	if commit != nil {
		if err := commit(); err != nil {
			e.stack.UnregisterTransportEndpoint(addr.NIC, netProtos, e.transProto, id)
			return err
		}
	}
	e.id = id
	e.regNICID = addr.NIC
	e.state = stateBound
	e.rcvMu.Lock()
	e.rcvReady = true
	e.rcvMu.Unlock()
	return nil
}