package socket
import (
"context"
"errors"
"net"
"net/netip"
"sync"
"syscall"
"time"
"github.com/tmc/go-iroh/key"
)
type Transports struct {
ip *IpTransport
relay *RelayTransport
custom []*customTransport
}
type MagicConn struct {
sock *Socket
transports *Transports
udp *net .UDPConn
localAddr net .Addr
recvCh chan recvBatch
readDeadline deadline
writeDeadline deadline
recvAddrs map [netip .AddrPort ]*net .UDPAddr
metrics Metrics
endpointMu sync .RWMutex
endpointSender func (key .EndpointID , []byte ) bool
}
func NewMagicConn (sock *Socket , udp *net .UDPConn ) *MagicConn {
return NewMagicConnWithRelay (sock , udp , nil )
}
func NewMagicConnWithRelay (sock *Socket , udp *net .UDPConn , actor *RelayActor ) *MagicConn {
return NewMagicConnWithTransports (sock , udp , actor )
}
func NewMagicConnWithTransports (sock *Socket , udp *net .UDPConn , actor *RelayActor , custom ...CustomTransport ) *MagicConn {
return newMagicConn (sock , udp , actor , custom ...)
}
func NewMagicConnRelayOnly (sock *Socket , actor *RelayActor , custom ...CustomTransport ) *MagicConn {
return newMagicConn (sock , nil , actor , custom ...)
}
func newMagicConn(sock *Socket , udp *net .UDPConn , actor *RelayActor , custom ...CustomTransport ) *MagicConn {
recvCh := make (chan recvBatch , 4 )
transports := &Transports {}
var localAddr net .Addr
if udp != nil {
transports .ip = NewIpTransport (udp , recvCh )
localAddr = udp .LocalAddr ()
} else {
localAddr = mappedUDPAddr (NewRelayMappedAddr ().Addr ())
}
if actor != nil {
transports .relay = NewRelayTransport (sock , actor , recvCh )
}
for _ , t := range custom {
if t != nil {
transports .custom = append (transports .custom , newCustomTransport (t , recvCh ))
}
}
m := &MagicConn {
sock : sock ,
transports : transports ,
udp : udp ,
localAddr : localAddr ,
recvCh : recvCh ,
recvAddrs : make (map [netip .AddrPort ]*net .UDPAddr ),
}
m .readDeadline .init ()
m .writeDeadline .init ()
if actor != nil {
actor .setMetrics (&m .metrics )
}
return m
}
func (m *MagicConn ) Relay () *RelayTransport { return m .transports .relay }
func (m *MagicConn ) SetEndpointSender (send func (key .EndpointID , []byte ) bool ) {
m .endpointMu .Lock ()
m .endpointSender = send
m .endpointMu .Unlock ()
}
func (m *MagicConn ) Serve (ctx context .Context ) {
if m .transports .ip == nil {
for _ , t := range m .transports .custom {
go t .Serve (ctx )
}
if m .transports .relay != nil {
m .transports .relay .Serve (ctx )
return
}
<-ctx .Done ()
return
}
if m .transports .relay != nil {
go m .transports .relay .Serve (ctx )
}
for _ , t := range m .transports .custom {
go t .Serve (ctx )
}
m .transports .ip .Serve (ctx )
}
func (m *MagicConn ) ReadFrom (p []byte ) (int , net .Addr , error ) {
for {
select {
case b := <- m .recvCh :
addr , ok := m .recvBatchAddr (b )
if !ok {
b .release ()
continue
}
m .recordRecv (b .recvAddr ())
n := copy (p , b .data )
b .release ()
return n , addr , nil
case <- m .readDeadline .wait ():
return 0 , nil , timeoutError {}
}
}
}
func (m *MagicConn ) recvBatchAddr (b recvBatch ) (net .Addr , bool ) {
if b .ip .IsValid () {
addr := m .udpAddr (b .ip )
return addr , true
}
addr , ok := m .recvAddr (b .info )
return addr , ok
}
func (m *MagicConn ) Metrics () MetricsSnapshot {
if m == nil {
return MetricsSnapshot {}
}
return m .metrics .snapshot ()
}
func (m *MagicConn ) MetricsSet () *Metrics {
if m == nil {
return nil
}
return &m .metrics
}
func (m *MagicConn ) RecordRelayHomeChange () {
if m != nil {
m .metrics .relayHomeChange .Add (1 )
}
}
func (m *MagicConn ) recvAddr (info RecvInfo ) (net .Addr , bool ) {
switch info .Remote .kind {
case AddrIP :
ap , _ := info .Remote .IP ()
addr := m .udpAddr (ap )
return addr , true
case AddrRelay :
url , eid , _ := info .Remote .Relay ()
mapped := m .sock .RelayMappedAddrFor (url , eid ).AddrPort ()
addr := m .udpAddr (mapped )
return addr , true
case AddrCustom :
c , _ := info .Remote .Custom ()
mapped := m .sock .CustomMappedAddrFor (c ).AddrPort ()
addr := m .udpAddr (mapped )
return addr , true
default :
return nil , false
}
}
func (m *MagicConn ) udpAddr (ap netip .AddrPort ) *net .UDPAddr {
ap = canonicalAddrPort (ap )
if addr , ok := m .recvAddrs [ap ]; ok {
return addr
}
addr := udpAddrFromAddrPort (ap )
m .recvAddrs [ap ] = addr
return addr
}
func mappedUDPAddr(a netip .Addr ) *net .UDPAddr {
return udpAddrFromAddrPort (netip .AddrPortFrom (a , mappedPort ))
}
func (m *MagicConn ) WriteTo (p []byte , addr net .Addr ) (int , error ) {
if m .sock .IsClosed () {
return len (p ), nil
}
if udp , ok := addr .(*net .UDPAddr ); ok {
ap := udp .AddrPort ()
if isDefinitelyIP (ap .Addr ()) || Classify (ap .Addr ()) == KindIP {
if m .transports .ip == nil {
m .metrics .blackholed .Add (1 )
return len (p ), nil
}
if _ , err := m .transports .ip .send (p , ap ); err == nil {
m .recordIPSent (ap )
} else {
m .metrics .blackholed .Add (1 )
}
return len (p ), nil
}
}
ap , ok := addrPort (addr )
if !ok {
return len (p ), nil
}
switch Classify (ap .Addr ()) {
case KindIP :
m .sendAddr (IPAddr (ap ), p )
return len (p ), nil
case KindEndpointID :
if id , ok := m .sock .LookupEndpointID (EndpointIDMappedAddrFromAddr (ap .Addr ())); ok {
m .endpointMu .RLock ()
send := m .endpointSender
m .endpointMu .RUnlock ()
if send != nil {
if send (id , p ) {
m .metrics .endpointIDSent .Add (1 )
} else {
m .metrics .blackholed .Add (1 )
}
} else {
m .metrics .blackholed .Add (1 )
}
} else {
m .metrics .blackholed .Add (1 )
}
return len (p ), nil
case KindRelay :
if addr , ok := relayAddrForMapped (m .sock , ap .Addr ()); ok {
m .sendAddr (addr , p )
} else {
m .metrics .blackholed .Add (1 )
}
return len (p ), nil
case KindCustom :
if c , ok := m .sock .LookupCustom (CustomMappedAddr {a : ap .Addr ()}); ok {
m .sendAddr (CustomAddr (c ), p )
} else {
m .metrics .blackholed .Add (1 )
}
return len (p ), nil
default :
m .metrics .blackholed .Add (1 )
return len (p ), nil
}
}
func isDefinitelyIP(addr netip .Addr ) bool {
if !addr .Is6 () {
return true
}
return addr .As16 ()[0 ] != 0xfd
}
func relayAddrForMapped(sock *Socket , mapped netip .Addr ) (Addr , bool ) {
if rk , ok := sock .LookupRelay (RelayMappedAddrFromAddr (mapped )); ok {
return RelayAddr (rk .URL , rk .EID ), true
}
return Addr {}, false
}
func (m *MagicConn ) sendAddr (addr Addr , p []byte ) bool {
switch addr .Kind () {
case AddrIP :
ap , _ := addr .IP ()
if !ap .IsValid () || ap .Port () == 0 {
m .metrics .blackholed .Add (1 )
return false
}
if m .transports .ip == nil {
m .metrics .blackholed .Add (1 )
return false
}
_ , err := m .transports .ip .send (p , ap )
if err == nil {
m .recordIPSent (ap )
return true
}
m .metrics .blackholed .Add (1 )
return false
case AddrRelay :
if m .transports .relay == nil {
m .metrics .blackholed .Add (1 )
return false
}
url , eid , _ := addr .Relay ()
mapped := m .sock .RelayMappedAddrFor (url , eid )
if m .transports .relay .Send (mapped , p ) {
m .metrics .relaySent .Add (1 )
return true
}
m .metrics .blackholed .Add (1 )
return false
case AddrCustom :
c , _ := addr .Custom ()
for _ , t := range m .transports .custom {
if t .Send (c , nil , p ) {
m .metrics .customSent .Add (1 )
return true
}
}
m .metrics .blackholed .Add (1 )
return false
default :
m .metrics .blackholed .Add (1 )
return false
}
}
func (m *MagicConn ) SendAddr (addr Addr , p []byte ) bool {
if m .sock .IsClosed () {
m .metrics .blackholed .Add (1 )
return false
}
return m .sendAddr (addr , p )
}
func (m *MagicConn ) recordRecv (addr Addr ) {
m .metrics .recvDatagrams .Add (1 )
switch addr .Kind () {
case AddrIP :
ap , _ := addr .IP ()
if ap .Addr ().Is4 () {
m .metrics .ipv4Recv .Add (1 )
} else {
m .metrics .ipv6Recv .Add (1 )
}
case AddrRelay :
m .metrics .relayRecv .Add (1 )
case AddrCustom :
m .metrics .customRecv .Add (1 )
}
}
func (m *MagicConn ) recordIPSent (ap netip .AddrPort ) {
if ap .Addr ().Is4 () {
m .metrics .ipv4Sent .Add (1 )
} else {
m .metrics .ipv6Sent .Add (1 )
}
}
func (m *MagicConn ) LocalAddr () net .Addr { return m .localAddr }
func (m *MagicConn ) Close () error {
m .sock .Close ()
m .readDeadline .set (time .Unix (0 , 1 ))
if m .udp == nil {
return nil
}
return m .udp .Close ()
}
func (m *MagicConn ) SetDeadline (t time .Time ) error {
m .readDeadline .set (t )
if m .udp == nil {
return nil
}
return m .udp .SetWriteDeadline (t )
}
func (m *MagicConn ) SetReadDeadline (t time .Time ) error {
m .readDeadline .set (t )
return nil
}
func (m *MagicConn ) SetWriteDeadline (t time .Time ) error {
if m .udp == nil {
return nil
}
return m .udp .SetWriteDeadline (t )
}
func (m *MagicConn ) SyscallConn () (syscall .RawConn , error ) {
if m .udp == nil {
return nil , errors .ErrUnsupported
}
return m .udp .SyscallConn ()
}
func (m *MagicConn ) SetReadBuffer (n int ) error {
if m .udp == nil {
return nil
}
return m .udp .SetReadBuffer (n )
}
func (m *MagicConn ) SetWriteBuffer (n int ) error {
if m .udp == nil {
return nil
}
return m .udp .SetWriteBuffer (n )
}
var _ net .PacketConn = (*MagicConn )(nil )
The pages are generated with Golds v0.8.4 . (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 .