mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
Packet delivery reads frozen under rcvMu, but freeze and thaw write it under mu. Use rcvMu for both sides and annotate the receive state so checklocks rejects this mismatch. Guard ident with mu and require it in bindLocked. Carry the selected identifier out of the synchronous network callbacks before assigning it under mu, keeping the ownership visible to the analyzer. Adapt the remaining coverage from #6642 to the current restore lifecycle. Co-authored-by: Hanzo Dev <[email protected]>
839 lines
23 KiB
Go
839 lines
23 KiB
Go
// Copyright 2018 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.
|
|
|
|
package icmp
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
"gvisor.dev/gvisor/pkg/tcpip/ports"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/internal/network"
|
|
"gvisor.dev/gvisor/pkg/waiter"
|
|
)
|
|
|
|
// +stateify savable
|
|
type icmpPacket struct {
|
|
icmpPacketEntry
|
|
senderAddress tcpip.FullAddress
|
|
packetInfo tcpip.IPPacketInfo
|
|
data *stack.PacketBuffer
|
|
receivedAt time.Time `state:".(int64)"`
|
|
|
|
// tosOrTClass stores either the Type of Service for IPv4 or the Traffic Class
|
|
// for IPv6.
|
|
tosOrTClass uint8
|
|
// ttlOrHopLimit stores either the TTL for IPv4 or the HopLimit for IPv6
|
|
ttlOrHopLimit uint8
|
|
}
|
|
|
|
// endpoint represents an ICMP endpoint. This struct serves as the interface
|
|
// between users of the endpoint and the protocol implementation; it is legal to
|
|
// have concurrent goroutines make calls into the endpoint, they are properly
|
|
// synchronized.
|
|
//
|
|
// +stateify savable
|
|
type endpoint struct {
|
|
tcpip.DefaultSocketOptionsHandler
|
|
|
|
// The following fields are initialized at creation time and are
|
|
// immutable.
|
|
stack *stack.Stack
|
|
transProto tcpip.TransportProtocolNumber
|
|
waiterQueue *waiter.Queue
|
|
net network.Endpoint
|
|
stats tcpip.TransportEndpointStats
|
|
ops tcpip.SocketOptions
|
|
|
|
// The following fields are used to manage the receive queue.
|
|
rcvMu sync.Mutex `state:"nosave"`
|
|
// +checklocks:rcvMu
|
|
rcvReady bool
|
|
// +checklocks:rcvMu
|
|
rcvList icmpPacketList
|
|
// +checklocks:rcvMu
|
|
rcvBufSize int
|
|
// +checklocks:rcvMu
|
|
rcvClosed bool
|
|
// frozen prevents packet delivery during save/restore.
|
|
// +checklocks:rcvMu
|
|
frozen bool
|
|
|
|
mu sync.RWMutex `state:"nosave"`
|
|
// +checklocks:mu
|
|
ident uint16
|
|
}
|
|
|
|
func newEndpoint(s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, waiterQueue *waiter.Queue) (tcpip.Endpoint, tcpip.Error) {
|
|
ep := &endpoint{
|
|
stack: s,
|
|
transProto: transProto,
|
|
waiterQueue: waiterQueue,
|
|
}
|
|
ep.ops.InitHandler(ep, ep.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits)
|
|
ep.ops.SetSendBufferSize(32*1024, false /* notify */)
|
|
ep.ops.SetReceiveBufferSize(32*1024, false /* notify */)
|
|
ep.net.Init(s, netProto, transProto, &ep.ops, waiterQueue)
|
|
|
|
// Override with stack defaults.
|
|
var ss tcpip.SendBufferSizeOption
|
|
if err := s.Option(&ss); err == nil {
|
|
ep.ops.SetSendBufferSize(int64(ss.Default), false /* notify */)
|
|
}
|
|
var rs tcpip.ReceiveBufferSizeOption
|
|
if err := s.Option(&rs); err == nil {
|
|
ep.ops.SetReceiveBufferSize(int64(rs.Default), false /* notify */)
|
|
}
|
|
return ep, nil
|
|
}
|
|
|
|
// WakeupWriters implements tcpip.SocketOptionsHandler.
|
|
func (e *endpoint) WakeupWriters() {
|
|
e.net.MaybeSignalWritable()
|
|
}
|
|
|
|
// Abort implements stack.TransportEndpoint.Abort.
|
|
func (e *endpoint) Abort() {
|
|
e.Close()
|
|
}
|
|
|
|
// Close puts the endpoint in a closed state and frees all resources
|
|
// associated with it.
|
|
func (e *endpoint) Close() {
|
|
notify := func() bool {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
switch state := e.net.State(); state {
|
|
case transport.DatagramEndpointStateInitial:
|
|
case transport.DatagramEndpointStateClosed:
|
|
return false
|
|
case transport.DatagramEndpointStateBound, transport.DatagramEndpointStateConnected:
|
|
info := e.net.Info()
|
|
info.ID.LocalPort = e.ident
|
|
e.stack.UnregisterTransportEndpoint([]tcpip.NetworkProtocolNumber{info.NetProto}, e.transProto, info.ID, e, ports.Flags{}, tcpip.NICID(e.ops.GetBindToDevice()))
|
|
default:
|
|
panic(fmt.Sprintf("unhandled state = %s", state))
|
|
}
|
|
|
|
e.net.Shutdown()
|
|
e.net.Close()
|
|
|
|
e.rcvMu.Lock()
|
|
defer e.rcvMu.Unlock()
|
|
e.rcvClosed = true
|
|
e.rcvBufSize = 0
|
|
for !e.rcvList.Empty() {
|
|
p := e.rcvList.Front()
|
|
e.rcvList.Remove(p)
|
|
p.data.DecRef()
|
|
}
|
|
|
|
return true
|
|
}()
|
|
|
|
if notify {
|
|
e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.ReadableEvents | waiter.WritableEvents)
|
|
}
|
|
}
|
|
|
|
// ModerateRecvBuf implements tcpip.Endpoint.ModerateRecvBuf.
|
|
func (*endpoint) ModerateRecvBuf(int) {}
|
|
|
|
// SetOwner implements tcpip.Endpoint.SetOwner.
|
|
func (e *endpoint) SetOwner(owner tcpip.PacketOwner) {
|
|
e.net.SetOwner(owner)
|
|
}
|
|
|
|
// Read implements tcpip.Endpoint.Read.
|
|
func (e *endpoint) Read(dst io.Writer, opts tcpip.ReadOptions) (tcpip.ReadResult, tcpip.Error) {
|
|
e.rcvMu.Lock()
|
|
|
|
if e.rcvList.Empty() {
|
|
var err tcpip.Error = &tcpip.ErrWouldBlock{}
|
|
if e.rcvClosed {
|
|
e.stats.ReadErrors.ReadClosed.Increment()
|
|
err = &tcpip.ErrClosedForReceive{}
|
|
}
|
|
e.rcvMu.Unlock()
|
|
return tcpip.ReadResult{}, err
|
|
}
|
|
|
|
p := e.rcvList.Front()
|
|
if opts.Peek {
|
|
p.data.IncRef()
|
|
} else {
|
|
e.rcvList.Remove(p)
|
|
e.rcvBufSize -= p.data.Data().Size()
|
|
}
|
|
defer p.data.DecRef()
|
|
|
|
e.rcvMu.Unlock()
|
|
|
|
// Control Messages
|
|
// TODO(https://gvisor.dev/issue/7012): Share control message code with other
|
|
// network endpoints.
|
|
cm := tcpip.ReceivableControlMessages{
|
|
HasTimestamp: true,
|
|
Timestamp: p.receivedAt,
|
|
}
|
|
switch netProto := e.net.NetProto(); netProto {
|
|
case header.IPv4ProtocolNumber:
|
|
if e.ops.GetReceiveTOS() {
|
|
cm.HasTOS = true
|
|
cm.TOS = p.tosOrTClass
|
|
}
|
|
if e.ops.GetReceivePacketInfo() {
|
|
cm.HasIPPacketInfo = true
|
|
cm.PacketInfo = p.packetInfo
|
|
}
|
|
if e.ops.GetReceiveTTL() {
|
|
cm.HasTTL = true
|
|
cm.TTL = p.ttlOrHopLimit
|
|
}
|
|
case header.IPv6ProtocolNumber:
|
|
if e.ops.GetReceiveTClass() {
|
|
cm.HasTClass = true
|
|
// Although TClass is an 8-bit value it's read in the CMsg as a uint32.
|
|
cm.TClass = uint32(p.tosOrTClass)
|
|
}
|
|
if e.ops.GetIPv6ReceivePacketInfo() {
|
|
cm.HasIPv6PacketInfo = true
|
|
cm.IPv6PacketInfo = tcpip.IPv6PacketInfo{
|
|
NIC: p.packetInfo.NIC,
|
|
Addr: p.packetInfo.DestinationAddr,
|
|
}
|
|
}
|
|
if e.ops.GetReceiveHopLimit() {
|
|
cm.HasHopLimit = true
|
|
cm.HopLimit = p.ttlOrHopLimit
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("unrecognized network protocol = %d", netProto))
|
|
}
|
|
|
|
res := tcpip.ReadResult{
|
|
Total: p.data.Data().Size(),
|
|
ControlMessages: cm,
|
|
}
|
|
if opts.NeedRemoteAddr {
|
|
res.RemoteAddr = p.senderAddress
|
|
}
|
|
|
|
n, err := p.data.Data().ReadTo(dst, opts.Peek)
|
|
if n == 0 && err != nil {
|
|
return res, &tcpip.ErrBadBuffer{}
|
|
}
|
|
res.Count = n
|
|
return res, nil
|
|
}
|
|
|
|
// prepareForWrite prepares the endpoint for sending data. In particular, it
|
|
// binds it if it's still in the initial state. To do so, it must first
|
|
// reacquire the mutex in exclusive mode.
|
|
//
|
|
// Returns true for retry if preparation should be retried.
|
|
// +checklocksread:e.mu
|
|
func (e *endpoint) prepareForWriteInner(to *tcpip.FullAddress) (retry bool, err tcpip.Error) {
|
|
switch e.net.State() {
|
|
case transport.DatagramEndpointStateInitial:
|
|
case transport.DatagramEndpointStateConnected:
|
|
return false, nil
|
|
case transport.DatagramEndpointStateBound:
|
|
if to == nil {
|
|
return false, &tcpip.ErrDestinationRequired{}
|
|
}
|
|
return false, nil
|
|
default:
|
|
return false, &tcpip.ErrInvalidEndpointState{}
|
|
}
|
|
|
|
e.mu.RUnlock()
|
|
e.mu.Lock()
|
|
defer e.mu.DowngradeLock()
|
|
|
|
// The state changed when we released the shared locked and re-acquired
|
|
// it in exclusive mode. Try again.
|
|
if e.net.State() != transport.DatagramEndpointStateInitial {
|
|
return true, nil
|
|
}
|
|
|
|
// The state is still 'initial', so try to bind the endpoint.
|
|
if err := e.bindLocked(tcpip.FullAddress{}); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Write writes data to the endpoint's peer. This method does not block
|
|
// if the data cannot be written.
|
|
func (e *endpoint) Write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, tcpip.Error) {
|
|
n, err := e.write(p, opts)
|
|
switch err.(type) {
|
|
case nil:
|
|
e.stats.PacketsSent.Increment()
|
|
case *tcpip.ErrMessageTooLong, *tcpip.ErrInvalidOptionValue:
|
|
e.stats.WriteErrors.InvalidArgs.Increment()
|
|
case *tcpip.ErrClosedForSend:
|
|
e.stats.WriteErrors.WriteClosed.Increment()
|
|
case *tcpip.ErrInvalidEndpointState:
|
|
e.stats.WriteErrors.InvalidEndpointState.Increment()
|
|
case *tcpip.ErrHostUnreachable, *tcpip.ErrBroadcastDisabled, *tcpip.ErrNetworkUnreachable:
|
|
// Errors indicating any problem with IP routing of the packet.
|
|
e.stats.SendErrors.NoRoute.Increment()
|
|
default:
|
|
// For all other errors when writing to the network layer.
|
|
e.stats.SendErrors.SendToNetworkFailed.Increment()
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (e *endpoint) prepareForWrite(opts tcpip.WriteOptions) (network.WriteContext, uint16, tcpip.Error) {
|
|
e.mu.RLock()
|
|
defer e.mu.RUnlock()
|
|
|
|
// Prepare for write.
|
|
for {
|
|
retry, err := e.prepareForWriteInner(opts.To)
|
|
if err != nil {
|
|
return network.WriteContext{}, 0, err
|
|
}
|
|
|
|
if !retry {
|
|
break
|
|
}
|
|
}
|
|
|
|
ctx, err := e.net.AcquireContextForWrite(opts)
|
|
return ctx, e.ident, err
|
|
}
|
|
|
|
func (e *endpoint) write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, tcpip.Error) {
|
|
ctx, ident, err := e.prepareForWrite(opts)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer ctx.Release()
|
|
|
|
// Prevents giant buffer allocations.
|
|
if p.Len() > header.DatagramMaximumSize {
|
|
return 0, &tcpip.ErrMessageTooLong{}
|
|
}
|
|
|
|
v := buffer.NewView(p.Len())
|
|
defer v.Release()
|
|
if _, err := io.CopyN(v, p, int64(p.Len())); err != nil {
|
|
return 0, &tcpip.ErrBadBuffer{}
|
|
}
|
|
n := v.Size()
|
|
|
|
switch netProto, pktInfo := e.net.NetProto(), ctx.PacketInfo(); netProto {
|
|
case header.IPv4ProtocolNumber:
|
|
if err := send4(e.stack, &ctx, ident, v, pktInfo.MaxHeaderLength); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
case header.IPv6ProtocolNumber:
|
|
if err := send6(e.stack, &ctx, ident, v, pktInfo.LocalAddress, pktInfo.RemoteAddress, pktInfo.MaxHeaderLength); err != nil {
|
|
return 0, err
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("unhandled network protocol = %d", netProto))
|
|
}
|
|
|
|
return int64(n), nil
|
|
}
|
|
|
|
var _ tcpip.SocketOptionsHandler = (*endpoint)(nil)
|
|
|
|
// HasNIC implements tcpip.SocketOptionsHandler.
|
|
func (e *endpoint) HasNIC(id int32) bool {
|
|
return e.stack.HasNIC(tcpip.NICID(id))
|
|
}
|
|
|
|
// SetSockOpt implements tcpip.Endpoint.
|
|
func (e *endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error {
|
|
return e.net.SetSockOpt(opt)
|
|
}
|
|
|
|
// SetSockOptInt implements tcpip.Endpoint.
|
|
func (e *endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
|
|
return e.net.SetSockOptInt(opt, v)
|
|
}
|
|
|
|
// GetSockOptInt implements tcpip.Endpoint.
|
|
func (e *endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
|
|
switch opt {
|
|
case tcpip.ReceiveQueueSizeOption:
|
|
v := 0
|
|
e.rcvMu.Lock()
|
|
if !e.rcvList.Empty() {
|
|
p := e.rcvList.Front()
|
|
v = p.data.Data().Size()
|
|
}
|
|
e.rcvMu.Unlock()
|
|
return v, nil
|
|
|
|
default:
|
|
return e.net.GetSockOptInt(opt)
|
|
}
|
|
}
|
|
|
|
// GetSockOpt implements tcpip.Endpoint.
|
|
func (e *endpoint) GetSockOpt(opt tcpip.GettableSocketOption) tcpip.Error {
|
|
return e.net.GetSockOpt(opt)
|
|
}
|
|
|
|
func send4(s *stack.Stack, ctx *network.WriteContext, ident uint16, data *buffer.View, maxHeaderLength uint16) tcpip.Error {
|
|
if data.Size() < header.ICMPv4MinimumSize {
|
|
return &tcpip.ErrInvalidEndpointState{}
|
|
}
|
|
|
|
pkt := ctx.TryNewPacketBuffer(header.ICMPv4MinimumSize+int(maxHeaderLength), buffer.Buffer{})
|
|
if pkt == nil {
|
|
return &tcpip.ErrWouldBlock{}
|
|
}
|
|
defer pkt.DecRef()
|
|
|
|
icmpv4 := header.ICMPv4(pkt.TransportHeader().Push(header.ICMPv4MinimumSize))
|
|
pkt.TransportProtocolNumber = header.ICMPv4ProtocolNumber
|
|
copy(icmpv4, data.AsSlice())
|
|
// Set the ident to the user-specified port. Sequence number should
|
|
// already be set by the user.
|
|
icmpv4.SetIdent(ident)
|
|
data.TrimFront(header.ICMPv4MinimumSize)
|
|
|
|
// Linux performs these basic checks.
|
|
if icmpv4.Type() != header.ICMPv4Echo || icmpv4.Code() != 0 {
|
|
return &tcpip.ErrInvalidEndpointState{}
|
|
}
|
|
|
|
icmpv4.SetChecksum(0)
|
|
icmpv4.SetChecksum(^checksum.Checksum(icmpv4, checksum.Checksum(data.AsSlice(), 0)))
|
|
pkt.Data().AppendView(data.Clone())
|
|
|
|
// Because this icmp endpoint is implemented in the transport layer, we can
|
|
// only increment the 'stack-wide' stats but we can't increment the
|
|
// 'per-NetworkEndpoint' stats.
|
|
stats := s.Stats().ICMP.V4.PacketsSent
|
|
|
|
if err := ctx.WritePacket(pkt, false /* headerIncluded */); err != nil {
|
|
stats.Dropped.Increment()
|
|
return err
|
|
}
|
|
|
|
stats.EchoRequest.Increment()
|
|
return nil
|
|
}
|
|
|
|
func send6(s *stack.Stack, ctx *network.WriteContext, ident uint16, data *buffer.View, src, dst tcpip.Address, maxHeaderLength uint16) tcpip.Error {
|
|
if data.Size() < header.ICMPv6EchoMinimumSize {
|
|
return &tcpip.ErrInvalidEndpointState{}
|
|
}
|
|
|
|
pkt := ctx.TryNewPacketBuffer(header.ICMPv6MinimumSize+int(maxHeaderLength), buffer.Buffer{})
|
|
if pkt == nil {
|
|
return &tcpip.ErrWouldBlock{}
|
|
}
|
|
defer pkt.DecRef()
|
|
|
|
icmpv6 := header.ICMPv6(pkt.TransportHeader().Push(header.ICMPv6MinimumSize))
|
|
pkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
|
|
copy(icmpv6, data.AsSlice())
|
|
// Set the ident. Sequence number is provided by the user.
|
|
icmpv6.SetIdent(ident)
|
|
data.TrimFront(header.ICMPv6MinimumSize)
|
|
|
|
if icmpv6.Type() != header.ICMPv6EchoRequest || icmpv6.Code() != 0 {
|
|
return &tcpip.ErrInvalidEndpointState{}
|
|
}
|
|
|
|
pkt.Data().AppendView(data.Clone())
|
|
pktData := pkt.Data()
|
|
icmpv6.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
|
Header: icmpv6,
|
|
Src: src,
|
|
Dst: dst,
|
|
PayloadCsum: pktData.Checksum(),
|
|
PayloadLen: pktData.Size(),
|
|
}))
|
|
|
|
// Because this icmp endpoint is implemented in the transport layer, we can
|
|
// only increment the 'stack-wide' stats but we can't increment the
|
|
// 'per-NetworkEndpoint' stats.
|
|
stats := s.Stats().ICMP.V6.PacketsSent
|
|
|
|
if err := ctx.WritePacket(pkt, false /* headerIncluded */); err != nil {
|
|
stats.Dropped.Increment()
|
|
return err
|
|
}
|
|
|
|
stats.EchoRequest.Increment()
|
|
return nil
|
|
}
|
|
|
|
// Disconnect implements tcpip.Endpoint.Disconnect.
|
|
func (*endpoint) Disconnect() tcpip.Error {
|
|
return &tcpip.ErrNotSupported{}
|
|
}
|
|
|
|
// Connect connects the endpoint to its peer. Specifying a NIC is optional.
|
|
func (e *endpoint) Connect(addr tcpip.FullAddress) tcpip.Error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
ident := e.ident
|
|
err := e.net.ConnectAndThen(addr, func(netProto tcpip.NetworkProtocolNumber, previousID, nextID stack.TransportEndpointID) tcpip.Error {
|
|
nextID.LocalPort = ident
|
|
|
|
nextID, err := e.registerWithStack(netProto, nextID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ident = nextID.LocalPort
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.ident = ident
|
|
|
|
e.rcvMu.Lock()
|
|
e.rcvReady = true
|
|
e.rcvMu.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConnectEndpoint is not supported.
|
|
func (*endpoint) ConnectEndpoint(tcpip.Endpoint) tcpip.Error {
|
|
return &tcpip.ErrInvalidEndpointState{}
|
|
}
|
|
|
|
// Shutdown closes the read and/or write end of the endpoint connection
|
|
// to its peer.
|
|
func (e *endpoint) Shutdown(flags tcpip.ShutdownFlags) tcpip.Error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
switch state := e.net.State(); state {
|
|
case transport.DatagramEndpointStateInitial, transport.DatagramEndpointStateClosed:
|
|
return &tcpip.ErrNotConnected{}
|
|
case transport.DatagramEndpointStateBound, transport.DatagramEndpointStateConnected:
|
|
default:
|
|
panic(fmt.Sprintf("unhandled state = %s", state))
|
|
}
|
|
|
|
if flags&tcpip.ShutdownWrite != 0 {
|
|
if err := e.net.Shutdown(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if flags&tcpip.ShutdownRead != 0 {
|
|
e.rcvMu.Lock()
|
|
wasClosed := e.rcvClosed
|
|
e.rcvClosed = true
|
|
e.rcvMu.Unlock()
|
|
|
|
if !wasClosed {
|
|
e.waiterQueue.Notify(waiter.ReadableEvents)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Listen is not supported by UDP, it just fails.
|
|
func (*endpoint) Listen(int) tcpip.Error {
|
|
return &tcpip.ErrNotSupported{}
|
|
}
|
|
|
|
// Accept is not supported by UDP, it just fails.
|
|
func (*endpoint) Accept(*tcpip.FullAddress) (tcpip.Endpoint, *waiter.Queue, tcpip.Error) {
|
|
return nil, nil, &tcpip.ErrNotSupported{}
|
|
}
|
|
|
|
func (e *endpoint) registerWithStack(netProto tcpip.NetworkProtocolNumber, id stack.TransportEndpointID) (stack.TransportEndpointID, tcpip.Error) {
|
|
bindToDevice := tcpip.NICID(e.ops.GetBindToDevice())
|
|
if id.LocalPort != 0 {
|
|
// The endpoint already has a local port, just attempt to
|
|
// register it.
|
|
return id, e.stack.RegisterTransportEndpoint([]tcpip.NetworkProtocolNumber{netProto}, e.transProto, id, e, ports.Flags{}, bindToDevice)
|
|
}
|
|
|
|
// We need to find a port for the endpoint.
|
|
_, err := e.stack.PickEphemeralPort(e.stack.SecureRNG(), func(p uint16) (bool, tcpip.Error) {
|
|
id.LocalPort = p
|
|
err := e.stack.RegisterTransportEndpoint([]tcpip.NetworkProtocolNumber{netProto}, e.transProto, id, e, ports.Flags{}, bindToDevice)
|
|
switch err.(type) {
|
|
case nil:
|
|
return true, nil
|
|
case *tcpip.ErrPortInUse:
|
|
return false, nil
|
|
default:
|
|
return false, err
|
|
}
|
|
})
|
|
|
|
return id, err
|
|
}
|
|
|
|
// +checklocks:e.mu
|
|
func (e *endpoint) bindLocked(addr tcpip.FullAddress) tcpip.Error {
|
|
// Don't allow binding once endpoint is not in the initial state
|
|
// anymore.
|
|
if e.net.State() != transport.DatagramEndpointStateInitial {
|
|
return &tcpip.ErrInvalidEndpointState{}
|
|
}
|
|
|
|
var ident uint16
|
|
err := e.net.BindAndThen(addr, func(boundNetProto tcpip.NetworkProtocolNumber, boundAddr tcpip.Address) tcpip.Error {
|
|
id := stack.TransportEndpointID{
|
|
LocalPort: addr.Port,
|
|
LocalAddress: addr.Addr,
|
|
}
|
|
id, err := e.registerWithStack(boundNetProto, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ident = id.LocalPort
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.ident = ident
|
|
|
|
e.rcvMu.Lock()
|
|
e.rcvReady = true
|
|
e.rcvMu.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *endpoint) isBroadcastOrMulticast(nicID tcpip.NICID, addr tcpip.Address) bool {
|
|
return addr == header.IPv4Broadcast ||
|
|
header.IsV4MulticastAddress(addr) ||
|
|
header.IsV6MulticastAddress(addr) ||
|
|
e.stack.IsSubnetBroadcast(nicID, e.net.NetProto(), addr)
|
|
}
|
|
|
|
// Bind binds the endpoint to a specific local address and port.
|
|
// Specifying a NIC is optional.
|
|
func (e *endpoint) Bind(addr tcpip.FullAddress) tcpip.Error {
|
|
if addr.Addr.BitLen() != 0 && e.isBroadcastOrMulticast(addr.NIC, addr.Addr) {
|
|
return &tcpip.ErrBadLocalAddress{}
|
|
}
|
|
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
return e.bindLocked(addr)
|
|
}
|
|
|
|
// GetLocalAddress returns the address to which the endpoint is bound.
|
|
func (e *endpoint) GetLocalAddress() (tcpip.FullAddress, tcpip.Error) {
|
|
e.mu.RLock()
|
|
defer e.mu.RUnlock()
|
|
|
|
addr := e.net.GetLocalAddress()
|
|
addr.Port = e.ident
|
|
return addr, nil
|
|
}
|
|
|
|
// GetRemoteAddress returns the address to which the endpoint is connected.
|
|
func (e *endpoint) GetRemoteAddress() (tcpip.FullAddress, tcpip.Error) {
|
|
e.mu.RLock()
|
|
defer e.mu.RUnlock()
|
|
|
|
if addr, connected := e.net.GetRemoteAddress(); connected {
|
|
return addr, nil
|
|
}
|
|
|
|
return tcpip.FullAddress{}, &tcpip.ErrNotConnected{}
|
|
}
|
|
|
|
// Readiness returns the current readiness of the endpoint. For example, if
|
|
// waiter.EventIn is set, the endpoint is immediately readable.
|
|
func (e *endpoint) Readiness(mask waiter.EventMask) waiter.EventMask {
|
|
var result waiter.EventMask
|
|
|
|
if e.net.HasSendSpace() {
|
|
result |= waiter.WritableEvents & mask
|
|
}
|
|
|
|
// Determine if the endpoint is readable if requested.
|
|
if (mask & waiter.ReadableEvents) != 0 {
|
|
e.rcvMu.Lock()
|
|
if !e.rcvList.Empty() || e.rcvClosed {
|
|
result |= waiter.ReadableEvents
|
|
}
|
|
e.rcvMu.Unlock()
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// HandlePacket is called by the stack when new packets arrive to this transport
|
|
// endpoint.
|
|
func (e *endpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) {
|
|
// Only accept echo replies.
|
|
switch e.net.NetProto() {
|
|
case header.IPv4ProtocolNumber:
|
|
h := header.ICMPv4(pkt.TransportHeader().Slice())
|
|
if len(h) < header.ICMPv4MinimumSize || h.Type() != header.ICMPv4EchoReply {
|
|
e.stack.Stats().DroppedPackets.Increment()
|
|
e.stats.ReceiveErrors.MalformedPacketsReceived.Increment()
|
|
return
|
|
}
|
|
case header.IPv6ProtocolNumber:
|
|
h := header.ICMPv6(pkt.TransportHeader().Slice())
|
|
if len(h) < header.ICMPv6MinimumSize || h.Type() != header.ICMPv6EchoReply {
|
|
e.stack.Stats().DroppedPackets.Increment()
|
|
e.stats.ReceiveErrors.MalformedPacketsReceived.Increment()
|
|
return
|
|
}
|
|
}
|
|
|
|
e.rcvMu.Lock()
|
|
|
|
// Drop the packet if our buffer is currently full.
|
|
if !e.rcvReady || e.rcvClosed {
|
|
e.rcvMu.Unlock()
|
|
e.stack.Stats().DroppedPackets.Increment()
|
|
e.stats.ReceiveErrors.ClosedReceiver.Increment()
|
|
return
|
|
}
|
|
|
|
rcvBufSize := e.ops.GetReceiveBufferSize()
|
|
if e.frozen || e.rcvBufSize >= int(rcvBufSize) {
|
|
e.rcvMu.Unlock()
|
|
e.stack.Stats().DroppedPackets.Increment()
|
|
e.stats.ReceiveErrors.ReceiveBufferOverflow.Increment()
|
|
return
|
|
}
|
|
|
|
wasEmpty := e.rcvBufSize == 0
|
|
|
|
net := pkt.Network()
|
|
dstAddr := net.DestinationAddress()
|
|
// Push new packet into receive list and increment the buffer size.
|
|
packet := &icmpPacket{
|
|
senderAddress: tcpip.FullAddress{
|
|
NIC: pkt.NICID,
|
|
Addr: id.RemoteAddress,
|
|
},
|
|
packetInfo: tcpip.IPPacketInfo{
|
|
// Linux does not 'prepare' [1] in_pktinfo on socket buffers destined to
|
|
// ping sockets (unlike UDP/RAW sockets). However the interface index [2]
|
|
// and the Header Destination Address [3] are always filled.
|
|
// [1] https://github.com/torvalds/linux/blob/dcb85f85fa6/net/ipv4/ip_sockglue.c#L1392
|
|
// [2] https://github.com/torvalds/linux/blob/dcb85f85fa6/net/ipv4/ip_input.c#L510
|
|
// [3] https://github.com/torvalds/linux/blob/dcb85f85fa6/net/ipv4/ip_sockglue.c#L60
|
|
NIC: pkt.NICID,
|
|
DestinationAddr: dstAddr,
|
|
},
|
|
}
|
|
|
|
// Save any useful information from the network header to the packet.
|
|
packet.tosOrTClass, _ = net.TOS()
|
|
switch pkt.NetworkProtocolNumber {
|
|
case header.IPv4ProtocolNumber:
|
|
packet.ttlOrHopLimit = header.IPv4(pkt.NetworkHeader().Slice()).TTL()
|
|
case header.IPv6ProtocolNumber:
|
|
packet.ttlOrHopLimit = header.IPv6(pkt.NetworkHeader().Slice()).HopLimit()
|
|
}
|
|
|
|
// ICMP socket's data includes ICMP header but no others. Trim all other
|
|
// headers from the front of the packet.
|
|
pktBuf := pkt.ToBuffer()
|
|
pktBuf.TrimFront(int64(pkt.HeaderSize() - len(pkt.TransportHeader().Slice())))
|
|
packet.data = stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: pktBuf})
|
|
|
|
e.rcvList.PushBack(packet)
|
|
e.rcvBufSize += packet.data.Data().Size()
|
|
|
|
packet.receivedAt = e.stack.Clock().Now()
|
|
|
|
e.rcvMu.Unlock()
|
|
e.stats.PacketsReceived.Increment()
|
|
// Notify any waiters that there's data to be read now.
|
|
if wasEmpty {
|
|
e.waiterQueue.Notify(waiter.ReadableEvents)
|
|
}
|
|
}
|
|
|
|
// HandleError implements stack.TransportEndpoint.
|
|
func (*endpoint) HandleError(stack.TransportError, *stack.PacketBuffer) {}
|
|
|
|
// State implements tcpip.Endpoint.State. The ICMP endpoint currently doesn't
|
|
// expose internal socket state.
|
|
func (e *endpoint) State() uint32 {
|
|
return uint32(e.net.State())
|
|
}
|
|
|
|
// Info returns a copy of the endpoint info.
|
|
func (e *endpoint) Info() tcpip.EndpointInfo {
|
|
e.mu.RLock()
|
|
defer e.mu.RUnlock()
|
|
ret := e.net.Info()
|
|
ret.ID.LocalPort = e.ident
|
|
return &ret
|
|
}
|
|
|
|
// Stats returns a pointer to the endpoint stats.
|
|
func (e *endpoint) Stats() tcpip.EndpointStats {
|
|
return &e.stats
|
|
}
|
|
|
|
// Wait implements stack.TransportEndpoint.Wait.
|
|
func (*endpoint) Wait() {}
|
|
|
|
// LastError implements tcpip.Endpoint.LastError.
|
|
func (*endpoint) LastError() tcpip.Error {
|
|
return nil
|
|
}
|
|
|
|
// SocketOptions implements tcpip.Endpoint.SocketOptions.
|
|
func (e *endpoint) SocketOptions() *tcpip.SocketOptions {
|
|
return &e.ops
|
|
}
|
|
|
|
// freeze prevents any more packets from being delivered to the endpoint.
|
|
func (e *endpoint) freeze() {
|
|
e.rcvMu.Lock()
|
|
e.frozen = true
|
|
e.rcvMu.Unlock()
|
|
}
|
|
|
|
// thaw unfreezes a previously frozen endpoint using endpoint.freeze() allows
|
|
// new packets to be delivered again.
|
|
func (e *endpoint) thaw() {
|
|
e.rcvMu.Lock()
|
|
e.frozen = false
|
|
e.rcvMu.Unlock()
|
|
}
|