gvisor-lx/pkg/tcpip/transport/internal/network/endpoint.go
Leadaxe 2c4ae3b0a4 snapshot: sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
Содержимое пина, зафиксированного в go.mod sing-box-lx, одним коммитом
без истории. Полная история SagerNet/gvisor — 1.45 ГБ и клонируется в
каждой CI-джобе; наша дельта — одна вставка в одну функцию, история для
неё не нужна.

Module path github.com/sagernet/gvisor сохранён намеренно: на него
опирается replace-директива суперпроекта.

Патч поверх — отдельным коммитом, чтобы дельта читалась одним git show
и переносилась на новый пин копированием.

SPECS/TASKS/048-GVISOR_HANDSHAKE_NIL_CRASH
2026-08-04 15:50:08 +03:00

1065 lines
30 KiB
Go

// 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.
// Package network provides facilities to support tcpip.Endpoints that operate
// at the network layer or above.
package network
import (
"fmt"
"github.com/sagernet/gvisor/pkg/atomicbitops"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/sync"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport"
"github.com/sagernet/gvisor/pkg/waiter"
)
// Endpoint is a datagram-based endpoint. It only supports sending datagrams to
// a peer.
//
// +stateify savable
type Endpoint struct {
// The following fields must only be set once then never changed.
stack *stack.Stack
ops *tcpip.SocketOptions
netProto tcpip.NetworkProtocolNumber
transProto tcpip.TransportProtocolNumber
waiterQueue *waiter.Queue
mu sync.RWMutex `state:"nosave"`
// +checklocks:mu
wasBound bool
// owner is the owner of transmitted packets.
//
// +checklocks:mu
owner tcpip.PacketOwner
// +checklocks:mu
writeShutdown bool
// +checklocks:mu
effectiveNetProto tcpip.NetworkProtocolNumber
// +checklocks:mu
connectedRoute *stack.Route `state:"nosave"`
// +checklocks:mu
multicastMemberships map[multicastMembership]struct{}
// +checklocks:mu
ipv4TTL uint8
// +checklocks:mu
ipv6HopLimit int16
// TODO(https://gvisor.dev/issue/6389): Use different fields for IPv4/IPv6.
// +checklocks:mu
multicastTTL uint8
// TODO(https://gvisor.dev/issue/6389): Use different fields for IPv4/IPv6.
// +checklocks:mu
multicastAddr tcpip.Address
// TODO(https://gvisor.dev/issue/6389): Use different fields for IPv4/IPv6.
// +checklocks:mu
multicastNICID tcpip.NICID
// +checklocks:mu
ipv4TOS uint8
// +checklocks:mu
ipv6TClass uint8
// Lock ordering: mu > infoMu.
infoMu sync.RWMutex `state:"nosave"`
// info has a dedicated mutex so that we can avoid lock ordering violations
// when reading the endpoint's info. If we used mu, we need to guarantee
// that any lock taken while mu is held is not held when calling Info()
// which is not true as of writing (we hold mu while registering transport
// endpoints (taking the transport demuxer lock but we also hold the demuxer
// lock when delivering packets/errors to endpoints).
//
// Writes must be performed through setInfo.
//
// +checklocks:infoMu
info stack.TransportEndpointInfo
// state holds a transport.DatagramBasedEndpointState.
//
// state must be accessed with atomics so that we can avoid lock ordering
// violations when reading the state. If we used mu, we need to guarantee
// that any lock taken while mu is held is not held when calling State()
// which is not true as of writing (we hold mu while registering transport
// endpoints (taking the transport demuxer lock but we also hold the demuxer
// lock when delivering packets/errors to endpoints).
//
// Writes must be performed through setEndpointState.
state atomicbitops.Uint32
// Callers should not attempt to obtain sendBufferSizeInUseMu while holding
// another lock on Endpoint.
sendBufferSizeInUseMu sync.RWMutex `state:"nosave"`
// sendBufferSizeInUse keeps track of the bytes in use by in-flight packets.
//
// +checklocks:sendBufferSizeInUseMu
sendBufferSizeInUse int64 `state:"nosave"`
}
// +stateify savable
type multicastMembership struct {
nicID tcpip.NICID
multicastAddr tcpip.Address
}
// Init initializes the endpoint.
func (e *Endpoint) Init(s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, ops *tcpip.SocketOptions, waiterQueue *waiter.Queue) {
e.mu.Lock()
defer e.mu.Unlock()
if e.multicastMemberships != nil {
panic(fmt.Sprintf("endpoint is already initialized; got e.multicastMemberships = %#v, want = nil", e.multicastMemberships))
}
switch netProto {
case header.IPv4ProtocolNumber, header.IPv6ProtocolNumber:
default:
panic(fmt.Sprintf("invalid protocol number = %d", netProto))
}
e.stack = s
e.ops = ops
e.netProto = netProto
e.transProto = transProto
e.waiterQueue = waiterQueue
e.infoMu.Lock()
e.info = stack.TransportEndpointInfo{
NetProto: netProto,
TransProto: transProto,
}
e.infoMu.Unlock()
e.effectiveNetProto = netProto
e.ipv4TTL = tcpip.UseDefaultIPv4TTL
e.ipv6HopLimit = tcpip.UseDefaultIPv6HopLimit
// Linux defaults to TTL=1.
e.multicastTTL = 1
e.multicastMemberships = make(map[multicastMembership]struct{})
e.setEndpointState(transport.DatagramEndpointStateInitial)
}
// NetProto returns the network protocol the endpoint was initialized with.
func (e *Endpoint) NetProto() tcpip.NetworkProtocolNumber {
return e.netProto
}
// setEndpointState sets the state of the endpoint.
//
// e.mu must be held to synchronize changes to state with the rest of the
// endpoint.
//
// +checklocks:e.mu
func (e *Endpoint) setEndpointState(state transport.DatagramEndpointState) {
e.state.Store(uint32(state))
}
// State returns the state of the endpoint.
func (e *Endpoint) State() transport.DatagramEndpointState {
return transport.DatagramEndpointState(e.state.Load())
}
// Close cleans the endpoint's resources and leaves the endpoint in a closed
// state.
func (e *Endpoint) Close() {
e.mu.Lock()
defer e.mu.Unlock()
if e.State() == transport.DatagramEndpointStateClosed {
return
}
for mem := range e.multicastMemberships {
e.stack.LeaveGroup(e.netProto, mem.nicID, mem.multicastAddr)
}
e.multicastMemberships = nil
if e.connectedRoute != nil {
e.connectedRoute.Release()
e.connectedRoute = nil
}
e.setEndpointState(transport.DatagramEndpointStateClosed)
}
// SetOwner sets the owner of transmitted packets.
func (e *Endpoint) SetOwner(owner tcpip.PacketOwner) {
e.mu.Lock()
defer e.mu.Unlock()
e.owner = owner
}
// +checklocksread:e.mu
func (e *Endpoint) calculateTTL(route *stack.Route) uint8 {
remoteAddress := route.RemoteAddress()
if header.IsV4MulticastAddress(remoteAddress) || header.IsV6MulticastAddress(remoteAddress) {
return e.multicastTTL
}
switch netProto := route.NetProto(); netProto {
case header.IPv4ProtocolNumber:
if e.ipv4TTL == 0 {
return route.DefaultTTL()
}
return e.ipv4TTL
case header.IPv6ProtocolNumber:
if e.ipv6HopLimit == -1 {
return route.DefaultTTL()
}
return uint8(e.ipv6HopLimit)
default:
panic(fmt.Sprintf("invalid protocol number = %d", netProto))
}
}
// WriteContext holds the context for a write.
type WriteContext struct {
e *Endpoint
route *stack.Route
ttl uint8
tos uint8
}
func (c *WriteContext) MTU() uint32 {
return c.route.MTU()
}
// Release releases held resources.
func (c *WriteContext) Release() {
c.route.Release()
*c = WriteContext{}
}
// WritePacketInfo is the properties of a packet that may be written.
type WritePacketInfo struct {
NetProto tcpip.NetworkProtocolNumber
LocalAddress, RemoteAddress tcpip.Address
MaxHeaderLength uint16
RequiresTXTransportChecksum bool
}
// PacketInfo returns the properties of a packet that will be written.
func (c *WriteContext) PacketInfo() WritePacketInfo {
return WritePacketInfo{
NetProto: c.route.NetProto(),
LocalAddress: c.route.LocalAddress(),
RemoteAddress: c.route.RemoteAddress(),
MaxHeaderLength: c.route.MaxHeaderLength(),
RequiresTXTransportChecksum: c.route.RequiresTXTransportChecksum(),
}
}
// TryNewPacketBuffer returns a new packet buffer iff the endpoint's send buffer
// is not full.
//
// If this method returns nil, the caller should wait for the endpoint to become
// writable.
func (c *WriteContext) TryNewPacketBuffer(reserveHdrBytes int, data buffer.Buffer) *stack.PacketBuffer {
e := c.e
e.sendBufferSizeInUseMu.Lock()
defer e.sendBufferSizeInUseMu.Unlock()
if !e.hasSendSpaceRLocked() {
return nil
}
return c.newPacketBufferLocked(reserveHdrBytes, data)
}
// TryNewPacketBufferFromPayloader returns a new packet buffer iff the endpoint's send buffer
// is not full. Otherwise, data from `payloader` isn't read.
//
// If this method returns nil, the caller should wait for the endpoint to become
// writable.
func (c *WriteContext) TryNewPacketBufferFromPayloader(reserveHdrBytes int, payloader tcpip.Payloader) *stack.PacketBuffer {
e := c.e
e.sendBufferSizeInUseMu.Lock()
defer e.sendBufferSizeInUseMu.Unlock()
if !e.hasSendSpaceRLocked() {
return nil
}
var data buffer.Buffer
if _, err := data.WriteFromReader(payloader, int64(payloader.Len())); err != nil {
data.Release()
return nil
}
return c.newPacketBufferLocked(reserveHdrBytes, data)
}
// +checklocks:c.e.sendBufferSizeInUseMu
func (c *WriteContext) newPacketBufferLocked(reserveHdrBytes int, data buffer.Buffer) *stack.PacketBuffer {
e := c.e
// Note that we allow oversubscription - if there is any space at all in the
// send buffer, we accept the full packet which may be larger than the space
// available. This is because if the endpoint reports that it is writable,
// a write operation should succeed.
//
// This matches Linux behaviour:
// https://github.com/torvalds/linux/blob/38d741cb70b/include/net/sock.h#L2519
// https://github.com/torvalds/linux/blob/38d741cb70b/net/core/sock.c#L2588
var expOptVal uint16
if nic, err := c.e.stack.GetNICByID(c.route.OutgoingNIC()); err == nil && nic.GetExperimentIPOptionEnabled() {
expOptVal = c.e.ops.GetExperimentOptionValue()
}
if c.route.NetProto() == header.IPv6ProtocolNumber && expOptVal != 0 {
reserveHdrBytes += header.IPv6ExperimentHdrLength
}
pktSize := int64(reserveHdrBytes) + int64(data.Size())
e.sendBufferSizeInUse += pktSize
return stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: reserveHdrBytes,
Payload: data,
OnRelease: func() {
e.sendBufferSizeInUseMu.Lock()
if got := e.sendBufferSizeInUse; got < pktSize {
e.sendBufferSizeInUseMu.Unlock()
panic(fmt.Sprintf("e.sendBufferSizeInUse=(%d) < pktSize(=%d)", got, pktSize))
}
e.sendBufferSizeInUse -= pktSize
signal := e.hasSendSpaceRLocked()
e.sendBufferSizeInUseMu.Unlock()
// Let waiters know if we now have space in the send buffer.
if signal {
e.waiterQueue.Notify(waiter.WritableEvents)
}
},
})
}
// WritePacket attempts to write the packet.
func (c *WriteContext) WritePacket(pkt *stack.PacketBuffer, headerIncluded bool) tcpip.Error {
c.e.mu.RLock()
pkt.Owner = c.e.owner
c.e.mu.RUnlock()
if headerIncluded {
return c.route.WriteHeaderIncludedPacket(pkt)
}
var expOptVal uint16
if nic, err := c.e.stack.GetNICByID(c.route.OutgoingNIC()); err == nil && nic.GetExperimentIPOptionEnabled() {
expOptVal = c.e.ops.GetExperimentOptionValue()
}
err := c.route.WritePacket(stack.NetworkHeaderParams{
Protocol: c.e.transProto,
TTL: c.ttl,
TOS: c.tos,
ExperimentOptionValue: expOptVal,
}, pkt)
if _, ok := err.(*tcpip.ErrNoBufferSpace); ok {
var recvErr bool
switch netProto := c.route.NetProto(); netProto {
case header.IPv4ProtocolNumber:
recvErr = c.e.ops.GetIPv4RecvError()
case header.IPv6ProtocolNumber:
recvErr = c.e.ops.GetIPv6RecvError()
default:
panic(fmt.Sprintf("unhandled network protocol number = %d", netProto))
}
// Linux only returns ENOBUFS to the caller if IP{,V6}_RECVERR is set.
//
// https://github.com/torvalds/linux/blob/3e71713c9e75c/net/ipv4/udp.c#L969
// https://github.com/torvalds/linux/blob/3e71713c9e75c/net/ipv6/udp.c#L1260
if !recvErr {
err = nil
}
}
return err
}
// MaybeSignalWritable signals waiters with writable events if the send buffer
// has space.
func (e *Endpoint) MaybeSignalWritable() {
e.sendBufferSizeInUseMu.RLock()
signal := e.hasSendSpaceRLocked()
e.sendBufferSizeInUseMu.RUnlock()
if signal {
e.waiterQueue.Notify(waiter.WritableEvents)
}
}
// HasSendSpace returns whether or not the send buffer has space.
func (e *Endpoint) HasSendSpace() bool {
e.sendBufferSizeInUseMu.RLock()
defer e.sendBufferSizeInUseMu.RUnlock()
return e.hasSendSpaceRLocked()
}
// +checklocksread:e.sendBufferSizeInUseMu
func (e *Endpoint) hasSendSpaceRLocked() bool {
return e.ops.GetSendBufferSize() > e.sendBufferSizeInUse
}
// AcquireContextForWrite acquires a WriteContext.
func (e *Endpoint) AcquireContextForWrite(opts tcpip.WriteOptions) (WriteContext, tcpip.Error) {
e.mu.RLock()
defer e.mu.RUnlock()
// MSG_MORE is unimplemented. This also means that MSG_EOR is a no-op.
if opts.More {
return WriteContext{}, &tcpip.ErrInvalidOptionValue{}
}
if e.State() == transport.DatagramEndpointStateClosed {
return WriteContext{}, &tcpip.ErrInvalidEndpointState{}
}
if e.writeShutdown {
return WriteContext{}, &tcpip.ErrClosedForSend{}
}
ipv6PktInfoValid := e.effectiveNetProto == header.IPv6ProtocolNumber && opts.ControlMessages.HasIPv6PacketInfo
route := e.connectedRoute
to := opts.To
info := e.Info()
switch {
case to == nil:
// If the user doesn't specify a destination, they should have
// connected to another address.
if e.State() != transport.DatagramEndpointStateConnected {
return WriteContext{}, &tcpip.ErrDestinationRequired{}
}
if !ipv6PktInfoValid {
route.Acquire()
break
}
// We are connected and the caller did not specify the destination but
// we have an IPv6 packet info structure which may change our local
// interface/address used to send the packet so we need to construct
// a new route instead of using the connected route.
//
// Construct a destination matching the remote the endpoint is connected
// to.
to = &tcpip.FullAddress{
// RegisterNICID is set when the endpoint is connected. It is usually
// only set for link-local addresses or multicast addresses if the
// multicast interface was specified (see e.multicastNICID,
// e.connectRouteRLocked and e.ConnectAndThen).
NIC: info.RegisterNICID,
Addr: info.ID.RemoteAddress,
}
fallthrough
default:
// Reject destination address if it goes through a different
// NIC than the endpoint was bound to.
nicID := to.NIC
if nicID == 0 {
nicID = tcpip.NICID(e.ops.GetBindToDevice())
}
var localAddr tcpip.Address
if ipv6PktInfoValid {
// Uphold strong-host semantics since (as of writing) the stack follows
// the strong host model.
pktInfoNICID := opts.ControlMessages.IPv6PacketInfo.NIC
pktInfoAddr := opts.ControlMessages.IPv6PacketInfo.Addr
if pktInfoNICID != 0 {
// If we are bound to an interface or specified the destination
// interface (usually when using link-local addresses), make sure the
// interface matches the specified local interface.
if nicID != 0 && nicID != pktInfoNICID {
return WriteContext{}, &tcpip.ErrHostUnreachable{}
}
// If a local address is not specified, then we need to make sure the
// bound address belongs to the specified local interface.
if pktInfoAddr.BitLen() == 0 {
// If the bound interface is different from the specified local
// interface, the bound address obviously does not belong to the
// specified local interface.
//
// The bound interface is usually only set for link-local addresses.
if info.BindNICID != 0 && info.BindNICID != pktInfoNICID {
return WriteContext{}, &tcpip.ErrHostUnreachable{}
}
if info.ID.LocalAddress.BitLen() != 0 && e.stack.CheckLocalAddress(pktInfoNICID, header.IPv6ProtocolNumber, info.ID.LocalAddress) == 0 {
return WriteContext{}, &tcpip.ErrBadLocalAddress{}
}
}
nicID = pktInfoNICID
}
if pktInfoAddr.BitLen() != 0 {
// The local address must belong to the stack. If an outgoing interface
// is specified as a result of binding the endpoint to a device, or
// specifying the outgoing interface in the destination address/pkt info
// structure, the address must belong to that interface.
if e.stack.CheckLocalAddress(nicID, header.IPv6ProtocolNumber, pktInfoAddr) == 0 {
return WriteContext{}, &tcpip.ErrBadLocalAddress{}
}
localAddr = pktInfoAddr
}
} else {
if info.BindNICID != 0 {
if nicID != 0 && nicID != info.BindNICID {
return WriteContext{}, &tcpip.ErrHostUnreachable{}
}
nicID = info.BindNICID
}
if nicID == 0 {
nicID = info.RegisterNICID
}
}
dst, netProto, err := e.checkV4Mapped(*to, false /* bind */)
if err != nil {
return WriteContext{}, err
}
route, _, err = e.connectRouteRLocked(nicID, localAddr, dst, netProto)
if err != nil {
return WriteContext{}, err
}
}
if !e.ops.GetBroadcast() && route.IsOutboundBroadcast() {
route.Release()
return WriteContext{}, &tcpip.ErrBroadcastDisabled{}
}
var tos uint8
var ttl uint8
switch netProto := route.NetProto(); netProto {
case header.IPv4ProtocolNumber:
tos = e.ipv4TOS
if opts.ControlMessages.HasTTL {
ttl = opts.ControlMessages.TTL
} else {
ttl = e.calculateTTL(route)
}
case header.IPv6ProtocolNumber:
tos = e.ipv6TClass
if opts.ControlMessages.HasHopLimit {
ttl = opts.ControlMessages.HopLimit
} else {
ttl = e.calculateTTL(route)
}
default:
panic(fmt.Sprintf("invalid protocol number = %d", netProto))
}
return WriteContext{
e: e,
route: route,
ttl: ttl,
tos: tos,
}, nil
}
// Disconnect disconnects the endpoint from its peer.
func (e *Endpoint) Disconnect() {
e.mu.Lock()
defer e.mu.Unlock()
if e.State() != transport.DatagramEndpointStateConnected {
return
}
info := e.Info()
// Exclude ephemerally bound endpoints.
if e.wasBound {
info.ID = stack.TransportEndpointID{
LocalAddress: info.BindAddr,
}
e.setEndpointState(transport.DatagramEndpointStateBound)
} else {
info.ID = stack.TransportEndpointID{}
e.setEndpointState(transport.DatagramEndpointStateInitial)
}
e.setInfo(info)
e.connectedRoute.Release()
e.connectedRoute = nil
}
// connectRouteRLocked establishes a route to the specified interface or the
// configured multicast interface if no interface is specified and the
// specified address is a multicast address.
//
// +checklocksread:e.mu
func (e *Endpoint) connectRouteRLocked(nicID tcpip.NICID, localAddr tcpip.Address, addr tcpip.FullAddress, netProto tcpip.NetworkProtocolNumber) (*stack.Route, tcpip.NICID, tcpip.Error) {
if localAddr.BitLen() == 0 {
localAddr = e.Info().ID.LocalAddress
if e.isBroadcastOrMulticast(nicID, netProto, localAddr) {
// A packet can only originate from a unicast address (i.e., an interface).
localAddr = tcpip.Address{}
}
if header.IsV4MulticastAddress(addr.Addr) || header.IsV6MulticastAddress(addr.Addr) {
if nicID == 0 {
nicID = e.multicastNICID
}
if localAddr == (tcpip.Address{}) && nicID == 0 {
localAddr = e.multicastAddr
}
}
}
// Find a route to the desired destination.
r, err := e.stack.FindRoute(nicID, localAddr, addr.Addr, netProto, e.ops.GetMulticastLoop())
if err != nil {
return nil, 0, err
}
return r, nicID, nil
}
// Connect connects the endpoint to the address.
func (e *Endpoint) Connect(addr tcpip.FullAddress) tcpip.Error {
return e.ConnectAndThen(addr, func(_ tcpip.NetworkProtocolNumber, _, _ stack.TransportEndpointID) tcpip.Error {
return nil
})
}
// ConnectAndThen connects the endpoint to the address and then calls the
// provided function.
//
// If the function returns an error, the endpoint's state does not change. The
// function will be called with the network protocol used to connect to the peer
// and the source and destination addresses that will be used to send traffic to
// the peer.
func (e *Endpoint) ConnectAndThen(addr tcpip.FullAddress, f func(netProto tcpip.NetworkProtocolNumber, previousID, nextID stack.TransportEndpointID) tcpip.Error) tcpip.Error {
addr.Port = 0
e.mu.Lock()
defer e.mu.Unlock()
info := e.Info()
nicID := addr.NIC
switch e.State() {
case transport.DatagramEndpointStateInitial:
case transport.DatagramEndpointStateBound, transport.DatagramEndpointStateConnected:
if info.BindNICID == 0 {
break
}
if nicID != 0 && nicID != info.BindNICID {
return &tcpip.ErrInvalidEndpointState{}
}
nicID = info.BindNICID
default:
return &tcpip.ErrInvalidEndpointState{}
}
addr, netProto, err := e.checkV4Mapped(addr, false /* bind */)
if err != nil {
return err
}
r, nicID, err := e.connectRouteRLocked(nicID, tcpip.Address{}, addr, netProto)
if err != nil {
return err
}
id := stack.TransportEndpointID{
LocalAddress: info.ID.LocalAddress,
RemoteAddress: r.RemoteAddress(),
}
if e.State() == transport.DatagramEndpointStateInitial {
id.LocalAddress = r.LocalAddress()
}
if err := f(r.NetProto(), info.ID, id); err != nil {
r.Release()
return err
}
if e.connectedRoute != nil {
// If the endpoint was previously connected then release any previous route.
e.connectedRoute.Release()
}
e.connectedRoute = r
info.ID = id
info.RegisterNICID = nicID
e.setInfo(info)
e.effectiveNetProto = netProto
e.setEndpointState(transport.DatagramEndpointStateConnected)
return nil
}
// Shutdown shutsdown the endpoint.
func (e *Endpoint) Shutdown() tcpip.Error {
e.mu.Lock()
defer e.mu.Unlock()
switch state := e.State(); state {
case transport.DatagramEndpointStateInitial, transport.DatagramEndpointStateClosed:
return &tcpip.ErrNotConnected{}
case transport.DatagramEndpointStateBound, transport.DatagramEndpointStateConnected:
e.writeShutdown = true
return nil
default:
panic(fmt.Sprintf("unhandled state = %s", state))
}
}
// checkV4MappedRLocked determines the effective network protocol and converts
// addr to its canonical form.
func (e *Endpoint) checkV4Mapped(addr tcpip.FullAddress, bind bool) (tcpip.FullAddress, tcpip.NetworkProtocolNumber, tcpip.Error) {
info := e.Info()
unwrapped, netProto, err := info.AddrNetProtoLocked(addr, e.ops.GetV6Only(), bind)
if err != nil {
return tcpip.FullAddress{}, 0, err
}
return unwrapped, netProto, nil
}
func (e *Endpoint) isBroadcastOrMulticast(nicID tcpip.NICID, netProto tcpip.NetworkProtocolNumber, addr tcpip.Address) bool {
return addr == header.IPv4Broadcast || header.IsV4MulticastAddress(addr) || header.IsV6MulticastAddress(addr) || e.stack.IsSubnetBroadcast(nicID, netProto, addr)
}
// Bind binds the endpoint to the address.
func (e *Endpoint) Bind(addr tcpip.FullAddress) tcpip.Error {
return e.BindAndThen(addr, func(tcpip.NetworkProtocolNumber, tcpip.Address) tcpip.Error {
return nil
})
}
// BindAndThen binds the endpoint to the address and then calls the provided
// function.
//
// If the function returns an error, the endpoint's state does not change. The
// function will be called with the bound network protocol and address.
func (e *Endpoint) BindAndThen(addr tcpip.FullAddress, f func(tcpip.NetworkProtocolNumber, tcpip.Address) tcpip.Error) tcpip.Error {
addr.Port = 0
e.mu.Lock()
defer e.mu.Unlock()
// Don't allow binding once endpoint is not in the initial state
// anymore.
if e.State() != transport.DatagramEndpointStateInitial {
return &tcpip.ErrInvalidEndpointState{}
}
addr, netProto, err := e.checkV4Mapped(addr, true /* bind */)
if err != nil {
return err
}
nicID := addr.NIC
if addr.Addr.BitLen() != 0 && !e.isBroadcastOrMulticast(addr.NIC, netProto, addr.Addr) {
nicID = e.stack.CheckLocalAddress(nicID, netProto, addr.Addr)
if nicID == 0 {
return &tcpip.ErrBadLocalAddress{}
}
}
if err := f(netProto, addr.Addr); err != nil {
return err
}
e.wasBound = true
info := e.Info()
info.ID = stack.TransportEndpointID{
LocalAddress: addr.Addr,
}
info.BindNICID = addr.NIC
info.RegisterNICID = nicID
info.BindAddr = addr.Addr
e.setInfo(info)
e.effectiveNetProto = netProto
e.setEndpointState(transport.DatagramEndpointStateBound)
return nil
}
// WasBound returns true iff the endpoint was ever bound.
func (e *Endpoint) WasBound() bool {
e.mu.RLock()
defer e.mu.RUnlock()
return e.wasBound
}
// GetLocalAddress returns the address that the endpoint is bound to.
func (e *Endpoint) GetLocalAddress() tcpip.FullAddress {
e.mu.RLock()
defer e.mu.RUnlock()
info := e.Info()
addr := info.BindAddr
if e.State() == transport.DatagramEndpointStateConnected {
addr = e.connectedRoute.LocalAddress()
}
return tcpip.FullAddress{
NIC: info.RegisterNICID,
Addr: addr,
}
}
// GetRemoteAddress returns the address that the endpoint is connected to.
func (e *Endpoint) GetRemoteAddress() (tcpip.FullAddress, bool) {
e.mu.RLock()
defer e.mu.RUnlock()
if e.State() != transport.DatagramEndpointStateConnected {
return tcpip.FullAddress{}, false
}
return tcpip.FullAddress{
Addr: e.connectedRoute.RemoteAddress(),
NIC: e.Info().RegisterNICID,
}, true
}
// SetSockOptInt sets the socket option.
func (e *Endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
switch opt {
case tcpip.MTUDiscoverOption:
// Return not supported if the value is not disabling path
// MTU discovery.
if tcpip.PMTUDStrategy(v) != tcpip.PMTUDiscoveryDont {
return &tcpip.ErrNotSupported{}
}
case tcpip.MulticastTTLOption:
e.mu.Lock()
e.multicastTTL = uint8(v)
e.mu.Unlock()
case tcpip.IPv4TTLOption:
e.mu.Lock()
e.ipv4TTL = uint8(v)
e.mu.Unlock()
case tcpip.IPv6HopLimitOption:
e.mu.Lock()
e.ipv6HopLimit = int16(v)
e.mu.Unlock()
case tcpip.IPv4TOSOption:
e.mu.Lock()
e.ipv4TOS = uint8(v)
e.mu.Unlock()
case tcpip.IPv6TrafficClassOption:
e.mu.Lock()
e.ipv6TClass = uint8(v)
e.mu.Unlock()
}
return nil
}
// GetSockOptInt returns the socket option.
func (e *Endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
switch opt {
case tcpip.MTUDiscoverOption:
// The only supported setting is path MTU discovery disabled.
return int(tcpip.PMTUDiscoveryDont), nil
case tcpip.MulticastTTLOption:
e.mu.Lock()
v := int(e.multicastTTL)
e.mu.Unlock()
return v, nil
case tcpip.IPv4TTLOption:
e.mu.Lock()
v := int(e.ipv4TTL)
e.mu.Unlock()
return v, nil
case tcpip.IPv6HopLimitOption:
e.mu.Lock()
v := int(e.ipv6HopLimit)
e.mu.Unlock()
return v, nil
case tcpip.IPv4TOSOption:
e.mu.RLock()
v := int(e.ipv4TOS)
e.mu.RUnlock()
return v, nil
case tcpip.IPv6TrafficClassOption:
e.mu.RLock()
v := int(e.ipv6TClass)
e.mu.RUnlock()
return v, nil
default:
return -1, &tcpip.ErrUnknownProtocolOption{}
}
}
// SetSockOpt sets the socket option.
func (e *Endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error {
switch v := opt.(type) {
case *tcpip.MulticastInterfaceOption:
e.mu.Lock()
defer e.mu.Unlock()
fa := tcpip.FullAddress{Addr: v.InterfaceAddr}
fa, netProto, err := e.checkV4Mapped(fa, true /* bind */)
if err != nil {
return err
}
nic := v.NIC
addr := fa.Addr
if nic == 0 && addr == (tcpip.Address{}) {
e.multicastAddr = tcpip.Address{}
e.multicastNICID = 0
break
}
if nic != 0 {
if !e.stack.CheckNIC(nic) {
return &tcpip.ErrBadLocalAddress{}
}
} else {
nic = e.stack.CheckLocalAddress(0, netProto, addr)
if nic == 0 {
return &tcpip.ErrBadLocalAddress{}
}
}
if info := e.Info(); info.BindNICID != 0 && info.BindNICID != nic {
return &tcpip.ErrInvalidEndpointState{}
}
e.multicastNICID = nic
e.multicastAddr = addr
case *tcpip.AddMembershipOption:
if !(header.IsV4MulticastAddress(v.MulticastAddr) && e.netProto == header.IPv4ProtocolNumber) && !(header.IsV6MulticastAddress(v.MulticastAddr) && e.netProto == header.IPv6ProtocolNumber) {
return &tcpip.ErrInvalidOptionValue{}
}
nicID := v.NIC
if v.InterfaceAddr.Unspecified() {
if nicID == 0 {
if r, err := e.stack.FindRoute(0, tcpip.Address{}, v.MulticastAddr, e.netProto, false /* multicastLoop */); err == nil {
nicID = r.NICID()
r.Release()
}
}
} else {
nicID = e.stack.CheckLocalAddress(nicID, e.netProto, v.InterfaceAddr)
}
if nicID == 0 {
return &tcpip.ErrUnknownDevice{}
}
memToInsert := multicastMembership{nicID: nicID, multicastAddr: v.MulticastAddr}
e.mu.Lock()
defer e.mu.Unlock()
if _, ok := e.multicastMemberships[memToInsert]; ok {
return &tcpip.ErrPortInUse{}
}
if err := e.stack.JoinGroup(e.netProto, nicID, v.MulticastAddr); err != nil {
return err
}
e.multicastMemberships[memToInsert] = struct{}{}
case *tcpip.RemoveMembershipOption:
if !(header.IsV4MulticastAddress(v.MulticastAddr) && e.netProto == header.IPv4ProtocolNumber) && !(header.IsV6MulticastAddress(v.MulticastAddr) && e.netProto == header.IPv6ProtocolNumber) {
return &tcpip.ErrInvalidOptionValue{}
}
nicID := v.NIC
if v.InterfaceAddr.Unspecified() {
if nicID == 0 {
if r, err := e.stack.FindRoute(0, tcpip.Address{}, v.MulticastAddr, e.netProto, false /* multicastLoop */); err == nil {
nicID = r.NICID()
r.Release()
}
}
} else {
nicID = e.stack.CheckLocalAddress(nicID, e.netProto, v.InterfaceAddr)
}
if nicID == 0 {
return &tcpip.ErrUnknownDevice{}
}
memToRemove := multicastMembership{nicID: nicID, multicastAddr: v.MulticastAddr}
e.mu.Lock()
defer e.mu.Unlock()
if _, ok := e.multicastMemberships[memToRemove]; !ok {
return &tcpip.ErrBadLocalAddress{}
}
if err := e.stack.LeaveGroup(e.netProto, nicID, v.MulticastAddr); err != nil {
return err
}
delete(e.multicastMemberships, memToRemove)
case *tcpip.SocketDetachFilterOption:
return nil
}
return nil
}
// GetSockOpt returns the socket option.
func (e *Endpoint) GetSockOpt(opt tcpip.GettableSocketOption) tcpip.Error {
switch o := opt.(type) {
case *tcpip.MulticastInterfaceOption:
e.mu.Lock()
*o = tcpip.MulticastInterfaceOption{
NIC: e.multicastNICID,
InterfaceAddr: e.multicastAddr,
}
e.mu.Unlock()
default:
return &tcpip.ErrUnknownProtocolOption{}
}
return nil
}
// Info returns a copy of the endpoint info.
func (e *Endpoint) Info() stack.TransportEndpointInfo {
e.infoMu.RLock()
defer e.infoMu.RUnlock()
return e.info
}
// setInfo sets the endpoint's info.
//
// e.mu must be held to synchronize changes to info with the rest of the
// endpoint.
//
// +checklocks:e.mu
func (e *Endpoint) setInfo(info stack.TransportEndpointInfo) {
e.infoMu.Lock()
defer e.infoMu.Unlock()
e.info = info
}