status-go/vendor/github.com/pion/ice/v2/candidate_base.go

573 lines
14 KiB
Go

// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ice
import (
"context"
"errors"
"fmt"
"hash/crc32"
"io"
"net"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/pion/stun"
)
type candidateBase struct {
id string
networkType NetworkType
candidateType CandidateType
component uint16
address string
port int
relatedAddress *CandidateRelatedAddress
tcpType TCPType
resolvedAddr net.Addr
lastSent atomic.Value
lastReceived atomic.Value
conn net.PacketConn
currAgent *Agent
closeCh chan struct{}
closedCh chan struct{}
foundationOverride string
priorityOverride uint32
remoteCandidateCaches map[AddrPort]Candidate
}
// Done implements context.Context
func (c *candidateBase) Done() <-chan struct{} {
return c.closeCh
}
// Err implements context.Context
func (c *candidateBase) Err() error {
select {
case <-c.closedCh:
return ErrRunCanceled
default:
return nil
}
}
// Deadline implements context.Context
func (c *candidateBase) Deadline() (deadline time.Time, ok bool) {
return time.Time{}, false
}
// Value implements context.Context
func (c *candidateBase) Value(interface{}) interface{} {
return nil
}
// ID returns Candidate ID
func (c *candidateBase) ID() string {
return c.id
}
func (c *candidateBase) Foundation() string {
if c.foundationOverride != "" {
return c.foundationOverride
}
return fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(c.Type().String()+c.address+c.networkType.String())))
}
// Address returns Candidate Address
func (c *candidateBase) Address() string {
return c.address
}
// Port returns Candidate Port
func (c *candidateBase) Port() int {
return c.port
}
// Type returns candidate type
func (c *candidateBase) Type() CandidateType {
return c.candidateType
}
// NetworkType returns candidate NetworkType
func (c *candidateBase) NetworkType() NetworkType {
return c.networkType
}
// Component returns candidate component
func (c *candidateBase) Component() uint16 {
return c.component
}
func (c *candidateBase) SetComponent(component uint16) {
c.component = component
}
// LocalPreference returns the local preference for this candidate
func (c *candidateBase) LocalPreference() uint16 {
if c.NetworkType().IsTCP() {
// RFC 6544, section 4.2
//
// In Section 4.1.2.1 of [RFC5245], a recommended formula for UDP ICE
// candidate prioritization is defined. For TCP candidates, the same
// formula and candidate type preferences SHOULD be used, and the
// RECOMMENDED type preferences for the new candidate types defined in
// this document (see Section 5) are 105 for NAT-assisted candidates and
// 75 for UDP-tunneled candidates.
//
// (...)
//
// With TCP candidates, the local preference part of the recommended
// priority formula is updated to also include the directionality
// (active, passive, or simultaneous-open) of the TCP connection. The
// RECOMMENDED local preference is then defined as:
//
// local preference = (2^13) * direction-pref + other-pref
//
// The direction-pref MUST be between 0 and 7 (both inclusive), with 7
// being the most preferred. The other-pref MUST be between 0 and 8191
// (both inclusive), with 8191 being the most preferred. It is
// RECOMMENDED that the host, UDP-tunneled, and relayed TCP candidates
// have the direction-pref assigned as follows: 6 for active, 4 for
// passive, and 2 for S-O. For the NAT-assisted and server reflexive
// candidates, the RECOMMENDED values are: 6 for S-O, 4 for active, and
// 2 for passive.
//
// (...)
//
// If any two candidates have the same type-preference and direction-
// pref, they MUST have a unique other-pref. With this specification,
// this usually only happens with multi-homed hosts, in which case
// other-pref is the preference for the particular IP address from which
// the candidate was obtained. When there is only a single IP address,
// this value SHOULD be set to the maximum allowed value (8191).
var otherPref uint16 = 8191
directionPref := func() uint16 {
switch c.Type() {
case CandidateTypeHost, CandidateTypeRelay:
switch c.tcpType {
case TCPTypeActive:
return 6
case TCPTypePassive:
return 4
case TCPTypeSimultaneousOpen:
return 2
case TCPTypeUnspecified:
return 0
}
case CandidateTypePeerReflexive, CandidateTypeServerReflexive:
switch c.tcpType {
case TCPTypeSimultaneousOpen:
return 6
case TCPTypeActive:
return 4
case TCPTypePassive:
return 2
case TCPTypeUnspecified:
return 0
}
case CandidateTypeUnspecified:
return 0
}
return 0
}()
return (1<<13)*directionPref + otherPref
}
return defaultLocalPreference
}
// RelatedAddress returns *CandidateRelatedAddress
func (c *candidateBase) RelatedAddress() *CandidateRelatedAddress {
return c.relatedAddress
}
func (c *candidateBase) TCPType() TCPType {
return c.tcpType
}
// start runs the candidate using the provided connection
func (c *candidateBase) start(a *Agent, conn net.PacketConn, initializedCh <-chan struct{}) {
if c.conn != nil {
c.agent().log.Warn("Can't start already started candidateBase")
return
}
c.currAgent = a
c.conn = conn
c.closeCh = make(chan struct{})
c.closedCh = make(chan struct{})
go c.recvLoop(initializedCh)
}
func (c *candidateBase) recvLoop(initializedCh <-chan struct{}) {
a := c.agent()
defer close(c.closedCh)
select {
case <-initializedCh:
case <-c.closeCh:
return
}
buf := make([]byte, receiveMTU)
for {
n, srcAddr, err := c.conn.ReadFrom(buf)
if err != nil {
if !(errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed)) {
a.log.Warnf("Failed to read from candidate %s: %v", c, err)
}
return
}
c.handleInboundPacket(buf[:n], srcAddr)
}
}
func (c *candidateBase) validateSTUNTrafficCache(addr net.Addr) bool {
if candidate, ok := c.remoteCandidateCaches[toAddrPort(addr)]; ok {
candidate.seen(false)
return true
}
return false
}
func (c *candidateBase) addRemoteCandidateCache(candidate Candidate, srcAddr net.Addr) {
if c.validateSTUNTrafficCache(srcAddr) {
return
}
c.remoteCandidateCaches[toAddrPort(srcAddr)] = candidate
}
func (c *candidateBase) handleInboundPacket(buf []byte, srcAddr net.Addr) {
a := c.agent()
if stun.IsMessage(buf) {
m := &stun.Message{
Raw: make([]byte, len(buf)),
}
// Explicitly copy raw buffer so Message can own the memory.
copy(m.Raw, buf)
if err := m.Decode(); err != nil {
a.log.Warnf("Failed to handle decode ICE from %s to %s: %v", c.addr(), srcAddr, err)
return
}
if err := a.run(c, func(ctx context.Context, a *Agent) {
// nolint: contextcheck
a.handleInbound(m, c, srcAddr)
}); err != nil {
a.log.Warnf("Failed to handle message: %v", err)
}
return
}
if !c.validateSTUNTrafficCache(srcAddr) {
remoteCandidate, valid := a.validateNonSTUNTraffic(c, srcAddr) //nolint:contextcheck
if !valid {
a.log.Warnf("Discarded message from %s, not a valid remote candidate", c.addr())
return
}
c.addRemoteCandidateCache(remoteCandidate, srcAddr)
}
// Note: This will return packetio.ErrFull if the buffer ever manages to fill up.
if _, err := a.buf.Write(buf); err != nil {
a.log.Warnf("Failed to write packet: %s", err)
return
}
}
// close stops the recvLoop
func (c *candidateBase) close() error {
// If conn has never been started will be nil
if c.Done() == nil {
return nil
}
// Assert that conn has not already been closed
select {
case <-c.Done():
return nil
default:
}
var firstErr error
// Unblock recvLoop
close(c.closeCh)
if err := c.conn.SetDeadline(time.Now()); err != nil {
firstErr = err
}
// Close the conn
if err := c.conn.Close(); err != nil && firstErr == nil {
firstErr = err
}
if firstErr != nil {
return firstErr
}
// Wait until the recvLoop is closed
<-c.closedCh
return nil
}
func (c *candidateBase) writeTo(raw []byte, dst Candidate) (int, error) {
n, err := c.conn.WriteTo(raw, dst.addr())
if err != nil {
// If the connection is closed, we should return the error
if errors.Is(err, io.ErrClosedPipe) {
return n, err
}
c.agent().log.Infof("Failed to send packet: %v", err)
return n, nil
}
c.seen(true)
return n, nil
}
// TypePreference returns the type preference for this candidate
func (c *candidateBase) TypePreference() uint16 {
pref := c.Type().Preference()
if pref == 0 {
return 0
}
if c.NetworkType().IsTCP() {
var tcpPriorityOffset uint16 = defaultTCPPriorityOffset
if c.agent() != nil {
tcpPriorityOffset = c.agent().tcpPriorityOffset
}
pref -= tcpPriorityOffset
}
return pref
}
// Priority computes the priority for this ICE Candidate
// See: https://www.rfc-editor.org/rfc/rfc8445#section-5.1.2.1
func (c *candidateBase) Priority() uint32 {
if c.priorityOverride != 0 {
return c.priorityOverride
}
// The local preference MUST be an integer from 0 (lowest preference) to
// 65535 (highest preference) inclusive. When there is only a single IP
// address, this value SHOULD be set to 65535. If there are multiple
// candidates for a particular component for a particular data stream
// that have the same type, the local preference MUST be unique for each
// one.
return (1<<24)*uint32(c.TypePreference()) +
(1<<8)*uint32(c.LocalPreference()) +
(1<<0)*uint32(256-c.Component())
}
// Equal is used to compare two candidateBases
func (c *candidateBase) Equal(other Candidate) bool {
return c.NetworkType() == other.NetworkType() &&
c.Type() == other.Type() &&
c.Address() == other.Address() &&
c.Port() == other.Port() &&
c.TCPType() == other.TCPType() &&
c.RelatedAddress().Equal(other.RelatedAddress())
}
// String makes the candidateBase printable
func (c *candidateBase) String() string {
return fmt.Sprintf("%s %s %s%s", c.NetworkType(), c.Type(), net.JoinHostPort(c.Address(), strconv.Itoa(c.Port())), c.relatedAddress)
}
// LastReceived returns a time.Time indicating the last time
// this candidate was received
func (c *candidateBase) LastReceived() time.Time {
if lastReceived, ok := c.lastReceived.Load().(time.Time); ok {
return lastReceived
}
return time.Time{}
}
func (c *candidateBase) setLastReceived(t time.Time) {
c.lastReceived.Store(t)
}
// LastSent returns a time.Time indicating the last time
// this candidate was sent
func (c *candidateBase) LastSent() time.Time {
if lastSent, ok := c.lastSent.Load().(time.Time); ok {
return lastSent
}
return time.Time{}
}
func (c *candidateBase) setLastSent(t time.Time) {
c.lastSent.Store(t)
}
func (c *candidateBase) seen(outbound bool) {
if outbound {
c.setLastSent(time.Now())
} else {
c.setLastReceived(time.Now())
}
}
func (c *candidateBase) addr() net.Addr {
return c.resolvedAddr
}
func (c *candidateBase) agent() *Agent {
return c.currAgent
}
func (c *candidateBase) context() context.Context {
return c
}
func (c *candidateBase) copy() (Candidate, error) {
return UnmarshalCandidate(c.Marshal())
}
func removeZoneIDFromAddress(addr string) string {
if i := strings.Index(addr, "%"); i != -1 {
return addr[:i]
}
return addr
}
// Marshal returns the string representation of the ICECandidate
func (c *candidateBase) Marshal() string {
val := c.Foundation()
if val == " " {
val = ""
}
val = fmt.Sprintf("%s %d %s %d %s %d typ %s",
val,
c.Component(),
c.NetworkType().NetworkShort(),
c.Priority(),
removeZoneIDFromAddress(c.Address()),
c.Port(),
c.Type())
if c.tcpType != TCPTypeUnspecified {
val += fmt.Sprintf(" tcptype %s", c.tcpType.String())
}
if r := c.RelatedAddress(); r != nil && r.Address != "" && r.Port != 0 {
val = fmt.Sprintf("%s raddr %s rport %d",
val,
r.Address,
r.Port)
}
return val
}
// UnmarshalCandidate creates a Candidate from its string representation
func UnmarshalCandidate(raw string) (Candidate, error) {
split := strings.Fields(raw)
// Foundation not specified: not RFC 8445 compliant but seen in the wild
if len(raw) != 0 && raw[0] == ' ' {
split = append([]string{" "}, split...)
}
if len(split) < 8 {
return nil, fmt.Errorf("%w (%d)", errAttributeTooShortICECandidate, len(split))
}
// Foundation
foundation := split[0]
// Component
rawComponent, err := strconv.ParseUint(split[1], 10, 16)
if err != nil {
return nil, fmt.Errorf("%w: %v", errParseComponent, err) //nolint:errorlint
}
component := uint16(rawComponent)
// Protocol
protocol := split[2]
// Priority
priorityRaw, err := strconv.ParseUint(split[3], 10, 32)
if err != nil {
return nil, fmt.Errorf("%w: %v", errParsePriority, err) //nolint:errorlint
}
priority := uint32(priorityRaw)
// Address
address := removeZoneIDFromAddress(split[4])
// Port
rawPort, err := strconv.ParseUint(split[5], 10, 16)
if err != nil {
return nil, fmt.Errorf("%w: %v", errParsePort, err) //nolint:errorlint
}
port := int(rawPort)
typ := split[7]
relatedAddress := ""
relatedPort := 0
tcpType := TCPTypeUnspecified
if len(split) > 8 {
split = split[8:]
if split[0] == "raddr" {
if len(split) < 4 {
return nil, fmt.Errorf("%w: incorrect length", errParseRelatedAddr)
}
// RelatedAddress
relatedAddress = split[1]
// RelatedPort
rawRelatedPort, parseErr := strconv.ParseUint(split[3], 10, 16)
if parseErr != nil {
return nil, fmt.Errorf("%w: %v", errParsePort, parseErr) //nolint:errorlint
}
relatedPort = int(rawRelatedPort)
} else if split[0] == "tcptype" {
if len(split) < 2 {
return nil, fmt.Errorf("%w: incorrect length", errParseTCPType)
}
tcpType = NewTCPType(split[1])
}
}
switch typ {
case "host":
return NewCandidateHost(&CandidateHostConfig{"", protocol, address, port, component, priority, foundation, tcpType})
case "srflx":
return NewCandidateServerReflexive(&CandidateServerReflexiveConfig{"", protocol, address, port, component, priority, foundation, relatedAddress, relatedPort})
case "prflx":
return NewCandidatePeerReflexive(&CandidatePeerReflexiveConfig{"", protocol, address, port, component, priority, foundation, relatedAddress, relatedPort})
case "relay":
return NewCandidateRelay(&CandidateRelayConfig{"", protocol, address, port, component, priority, foundation, relatedAddress, relatedPort, "", nil})
default:
}
return nil, fmt.Errorf("%w (%s)", ErrUnknownCandidateTyp, typ)
}