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

234 lines
4.9 KiB
Go

package ice
import (
"fmt"
"net"
"sync/atomic"
"time"
"github.com/pion/logging"
"github.com/pion/stun"
"github.com/pion/transport/vnet"
)
type atomicError struct{ v atomic.Value }
func (a *atomicError) Store(err error) {
a.v.Store(struct{ error }{err})
}
func (a *atomicError) Load() error {
err, _ := a.v.Load().(struct{ error })
return err.error
}
// The conditions of invalidation written below are defined in
// https://tools.ietf.org/html/rfc8445#section-5.1.1.1
func isSupportedIPv6(ip net.IP) bool {
if len(ip) != net.IPv6len ||
isZeros(ip[0:12]) || // !(IPv4-compatible IPv6)
ip[0] == 0xfe && ip[1]&0xc0 == 0xc0 || // !(IPv6 site-local unicast)
ip.IsLinkLocalUnicast() ||
ip.IsLinkLocalMulticast() {
return false
}
return true
}
func isZeros(ip net.IP) bool {
for i := 0; i < len(ip); i++ {
if ip[i] != 0 {
return false
}
}
return true
}
func parseAddr(in net.Addr) (net.IP, int, NetworkType, bool) {
switch addr := in.(type) {
case *net.UDPAddr:
return addr.IP, addr.Port, NetworkTypeUDP4, true
case *net.TCPAddr:
return addr.IP, addr.Port, NetworkTypeTCP4, true
}
return nil, 0, 0, false
}
func createAddr(network NetworkType, ip net.IP, port int) net.Addr {
switch {
case network.IsTCP():
return &net.TCPAddr{IP: ip, Port: port}
default:
return &net.UDPAddr{IP: ip, Port: port}
}
}
func addrEqual(a, b net.Addr) bool {
aIP, aPort, aType, aOk := parseAddr(a)
if !aOk {
return false
}
bIP, bPort, bType, bOk := parseAddr(b)
if !bOk {
return false
}
return aType == bType && aIP.Equal(bIP) && aPort == bPort
}
// getXORMappedAddr initiates a stun requests to serverAddr using conn, reads the response and returns
// the XORMappedAddress returned by the stun server.
//
// Adapted from stun v0.2.
func getXORMappedAddr(conn net.PacketConn, serverAddr net.Addr, deadline time.Duration) (*stun.XORMappedAddress, error) {
if deadline > 0 {
if err := conn.SetReadDeadline(time.Now().Add(deadline)); err != nil {
return nil, err
}
}
defer func() {
if deadline > 0 {
_ = conn.SetReadDeadline(time.Time{})
}
}()
resp, err := stunRequest(
func(p []byte) (int, error) {
n, _, errr := conn.ReadFrom(p)
return n, errr
},
func(b []byte) (int, error) {
return conn.WriteTo(b, serverAddr)
},
)
if err != nil {
return nil, err
}
var addr stun.XORMappedAddress
if err = addr.GetFrom(resp); err != nil {
return nil, fmt.Errorf("%w: %v", errGetXorMappedAddrResponse, err)
}
return &addr, nil
}
func stunRequest(read func([]byte) (int, error), write func([]byte) (int, error)) (*stun.Message, error) {
req, err := stun.Build(stun.BindingRequest, stun.TransactionID)
if err != nil {
return nil, err
}
if _, err = write(req.Raw); err != nil {
return nil, err
}
const maxMessageSize = 1280
bs := make([]byte, maxMessageSize)
n, err := read(bs)
if err != nil {
return nil, err
}
res := &stun.Message{Raw: bs[:n]}
if err := res.Decode(); err != nil {
return nil, err
}
return res, nil
}
func localInterfaces(vnet *vnet.Net, interfaceFilter func(string) bool, networkTypes []NetworkType) ([]net.IP, error) { //nolint:gocognit
ips := []net.IP{}
ifaces, err := vnet.Interfaces()
if err != nil {
return ips, err
}
var IPv4Requested, IPv6Requested bool
for _, typ := range networkTypes {
if typ.IsIPv4() {
IPv4Requested = true
}
if typ.IsIPv6() {
IPv6Requested = true
}
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 {
continue // interface down
}
if iface.Flags&net.FlagLoopback != 0 {
continue // loopback interface
}
if interfaceFilter != nil && !interfaceFilter(iface.Name) {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
var ip net.IP
switch addr := addr.(type) {
case *net.IPNet:
ip = addr.IP
case *net.IPAddr:
ip = addr.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
if ipv4 := ip.To4(); ipv4 == nil {
if !IPv6Requested {
continue
} else if !isSupportedIPv6(ip) {
continue
}
} else if !IPv4Requested {
continue
}
ips = append(ips, ip)
}
}
return ips, nil
}
func listenUDPInPortRange(vnet *vnet.Net, log logging.LeveledLogger, portMax, portMin int, network string, laddr *net.UDPAddr) (vnet.UDPPacketConn, error) {
if (laddr.Port != 0) || ((portMin == 0) && (portMax == 0)) {
return vnet.ListenUDP(network, laddr)
}
var i, j int
i = portMin
if i == 0 {
i = 1
}
j = portMax
if j == 0 {
j = 0xFFFF
}
if i > j {
return nil, ErrPort
}
portStart := globalMathRandomGenerator.Intn(j-i+1) + i
portCurrent := portStart
for {
laddr = &net.UDPAddr{IP: laddr.IP, Port: portCurrent}
c, e := vnet.ListenUDP(network, laddr)
if e == nil {
return c, e
}
log.Debugf("failed to listen %s: %v", laddr.String(), e)
portCurrent++
if portCurrent > j {
portCurrent = i
}
if portCurrent == portStart {
break
}
}
return nil, ErrPort
}