package ice
import (
"fmt"
"net"
"time"
"github.com/pion/logging"
"github.com/pion/stun/v3"
"github.com/pion/transport/v3"
)
type UniversalUDPMux interface {
UDPMux
GetXORMappedAddr (stunAddr net .Addr , deadline time .Duration ) (*stun .XORMappedAddress , error )
GetRelayedAddr (turnAddr net .Addr , deadline time .Duration ) (*net .Addr , error )
GetConnForURL (ufrag string , url string , addr net .Addr ) (net .PacketConn , error )
}
type UniversalUDPMuxDefault struct {
*UDPMuxDefault
params UniversalUDPMuxParams
xorMappedMap map [string ]*xorMapped
}
type UniversalUDPMuxParams struct {
Logger logging .LeveledLogger
UDPConn net .PacketConn
XORMappedAddrCacheTTL time .Duration
Net transport .Net
}
func NewUniversalUDPMuxDefault (params UniversalUDPMuxParams ) *UniversalUDPMuxDefault {
if params .Logger == nil {
params .Logger = logging .NewDefaultLoggerFactory ().NewLogger ("ice" )
}
if params .XORMappedAddrCacheTTL == 0 {
params .XORMappedAddrCacheTTL = time .Second * 25
}
mux := &UniversalUDPMuxDefault {
params : params ,
xorMappedMap : make (map [string ]*xorMapped ),
}
mux .params .UDPConn = &udpConn {
PacketConn : params .UDPConn ,
mux : mux ,
logger : params .Logger ,
}
udpMuxParams := UDPMuxParams {
Logger : params .Logger ,
UDPConn : mux .params .UDPConn ,
Net : mux .params .Net ,
}
mux .UDPMuxDefault = NewUDPMuxDefault (udpMuxParams )
return mux
}
type udpConn struct {
net .PacketConn
mux *UniversalUDPMuxDefault
logger logging .LeveledLogger
}
func (m *UniversalUDPMuxDefault ) GetRelayedAddr (net .Addr , time .Duration ) (*net .Addr , error ) {
return nil , errNotImplemented
}
func (m *UniversalUDPMuxDefault ) GetConnForURL (ufrag string , url string , addr net .Addr ) (net .PacketConn , error ) {
return m .UDPMuxDefault .GetConn (fmt .Sprintf ("%s%s" , ufrag , url ), addr )
}
func (c *udpConn ) ReadFrom (p []byte ) (n int , addr net .Addr , err error ) {
n , addr , err = c .PacketConn .ReadFrom (p )
if err != nil {
return n , addr , err
}
if stun .IsMessage (p [:n ]) {
msg := &stun .Message {
Raw : append ([]byte {}, p [:n ]...),
}
if err = msg .Decode (); err != nil {
c .logger .Warnf ("Failed to handle decode ICE from %s: %v" , addr .String (), err )
return n , addr , nil
}
udpAddr , ok := addr .(*net .UDPAddr )
if !ok {
return n , addr , err
}
if c .mux .isXORMappedResponse (msg , udpAddr .String ()) {
err = c .mux .handleXORMappedResponse (udpAddr , msg )
if err != nil {
c .logger .Debugf ("%w: %v" , errGetXorMappedAddrResponse , err )
err = nil
}
return n , addr , err
}
}
return n , addr , err
}
func (m *UniversalUDPMuxDefault ) isXORMappedResponse (msg *stun .Message , stunAddr string ) bool {
m .mu .Lock ()
defer m .mu .Unlock ()
_ , ok := m .xorMappedMap [stunAddr ]
_ , err := msg .Get (stun .AttrXORMappedAddress )
return err == nil && ok
}
func (m *UniversalUDPMuxDefault ) handleXORMappedResponse (stunAddr *net .UDPAddr , msg *stun .Message ) error {
m .mu .Lock ()
defer m .mu .Unlock ()
mappedAddr , ok := m .xorMappedMap [stunAddr .String ()]
if !ok {
return errNoXorAddrMapping
}
var addr stun .XORMappedAddress
if err := addr .GetFrom (msg ); err != nil {
return err
}
m .xorMappedMap [stunAddr .String ()] = mappedAddr
mappedAddr .SetAddr (&addr )
return nil
}
func (m *UniversalUDPMuxDefault ) GetXORMappedAddr (
serverAddr net .Addr ,
deadline time .Duration ,
) (*stun .XORMappedAddress , error ) {
m .mu .Lock ()
mappedAddr , ok := m .xorMappedMap [serverAddr .String ()]
if ok {
if mappedAddr .expired () {
mappedAddr .closeWaiters ()
delete (m .xorMappedMap , serverAddr .String ())
ok = false
} else if mappedAddr .pending () {
ok = false
}
}
m .mu .Unlock ()
if ok {
return mappedAddr .addr , nil
}
waitAddrReceived , err := m .writeSTUN (serverAddr )
if err != nil {
return nil , fmt .Errorf ("%w: %s" , errWriteSTUNMessage , err )
}
select {
case <- waitAddrReceived :
m .mu .Lock ()
mappedAddr := *m .xorMappedMap [serverAddr .String ()]
m .mu .Unlock ()
if mappedAddr .addr == nil {
return nil , errNoXorAddrMapping
}
return mappedAddr .addr , nil
case <- time .After (deadline ):
return nil , errXORMappedAddrTimeout
}
}
func (m *UniversalUDPMuxDefault ) writeSTUN (serverAddr net .Addr ) (chan struct {}, error ) {
m .mu .Lock ()
defer m .mu .Unlock ()
addrMap , ok := m .xorMappedMap [serverAddr .String ()]
if !ok {
addrMap = &xorMapped {
expiresAt : time .Now ().Add (m .params .XORMappedAddrCacheTTL ),
waitAddrReceived : make (chan struct {}),
}
m .xorMappedMap [serverAddr .String ()] = addrMap
}
req , err := stun .Build (stun .BindingRequest , stun .TransactionID )
if err != nil {
return nil , err
}
if _, err = m .params .UDPConn .WriteTo (req .Raw , serverAddr ); err != nil {
return nil , err
}
return addrMap .waitAddrReceived , nil
}
type xorMapped struct {
addr *stun .XORMappedAddress
waitAddrReceived chan struct {}
expiresAt time .Time
}
func (a *xorMapped ) closeWaiters () {
select {
case <- a .waitAddrReceived :
break
default :
close (a .waitAddrReceived )
}
}
func (a *xorMapped ) pending () bool {
return a .addr == nil
}
func (a *xorMapped ) expired () bool {
return a .expiresAt .Before (time .Now ())
}
func (a *xorMapped ) SetAddr (addr *stun .XORMappedAddress ) {
a .addr = addr
a .closeWaiters ()
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .