package iroh
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/netip"
"sync"
"time"
quic "github.com/tmc/go-iroh/internal/qng"
"github.com/tmc/go-iroh/internal/socket"
"github.com/tmc/go-iroh/key"
"github.com/tmc/go-iroh/netaddr"
)
type Side int
const (
SideClient Side = iota
SideServer
)
func (s Side ) String () string {
switch s {
case SideClient :
return "client"
case SideServer :
return "server"
default :
return "unknown"
}
}
type Stream struct {
s *quic .Stream
}
type SendStream struct {
s *quic .SendStream
}
type ReceiveStream struct {
s *quic .ReceiveStream
}
func (s *Stream ) Read (p []byte ) (int , error ) { return s .s .Read (p ) }
func (s *Stream ) Write (p []byte ) (int , error ) { return s .s .Write (p ) }
func (s *Stream ) ReadFrom (r io .Reader ) (int64 , error ) { return s .s .ReadFrom (r ) }
func (s *Stream ) Writev (bufs *net .Buffers ) (int64 , error ) { return s .s .Writev (bufs ) }
func (s *Stream ) Close () error { return s .s .Close () }
func (s *Stream ) SetDeadline (t time .Time ) error { return s .s .SetDeadline (t ) }
func (s *Stream ) SetReadDeadline (t time .Time ) error { return s .s .SetReadDeadline (t ) }
func (s *Stream ) SetWriteDeadline (t time .Time ) error { return s .s .SetWriteDeadline (t ) }
func (s *Stream ) CancelRead (code uint64 ) { s .s .CancelRead (quic .StreamErrorCode (code )) }
func (s *Stream ) CancelWrite (code uint64 ) { s .s .CancelWrite (quic .StreamErrorCode (code )) }
func (s *Stream ) Context () context .Context { return s .s .Context () }
func (s *SendStream ) Write (p []byte ) (int , error ) { return s .s .Write (p ) }
func (s *SendStream ) ReadFrom (r io .Reader ) (int64 , error ) { return s .s .ReadFrom (r ) }
func (s *SendStream ) Writev (bufs *net .Buffers ) (int64 , error ) { return s .s .Writev (bufs ) }
func (s *SendStream ) Close () error { return s .s .Close () }
func (s *SendStream ) SetWriteDeadline (t time .Time ) error { return s .s .SetWriteDeadline (t ) }
func (s *SendStream ) CancelWrite (code uint64 ) { s .s .CancelWrite (quic .StreamErrorCode (code )) }
func (s *SendStream ) Context () context .Context { return s .s .Context () }
func (s *ReceiveStream ) Read (p []byte ) (int , error ) { return s .s .Read (p ) }
func (s *ReceiveStream ) SetReadDeadline (t time .Time ) error { return s .s .SetReadDeadline (t ) }
func (s *ReceiveStream ) CancelRead (code uint64 ) { s .s .CancelRead (quic .StreamErrorCode (code )) }
type Conn struct {
qc *quic .Conn
remoteID key .EndpointID
alpn string
side Side
stableID uint64
resolveOnce sync .Once
resolve func () (key .EndpointID , string )
pathState *socket .RemoteStateActor
pathConn *connAdapter
}
func (c *Conn ) resolveIdentity () {
if c .resolve == nil {
return
}
c .resolveOnce .Do (func () {
c .remoteID , c .alpn = c .resolve ()
})
}
type ConnStats struct {
MinRTT time .Duration
LatestRTT time .Duration
SmoothedRTT time .Duration
MeanDeviation time .Duration
BytesSent uint64
PacketsSent uint64
BytesReceived uint64
PacketsReceived uint64
BytesLost uint64
PacketsLost uint64
}
type PathInfo struct {
ID uint32
Validated bool
Addr netaddr .TransportAddr
HasAddr bool
RTT time .Duration
HasRTT bool
BytesInFlight uint64
HasBytesInFlight bool
BytesSent uint64
HasBytesSent bool
BytesReceived uint64
HasBytesReceived bool
CongestionWindow uint64
HasCongestionWindow bool
LostPackets uint64
LostBytes uint64
HasLoss bool
Selected bool
Relayed bool
}
func newConn(qc *quic .Conn , remoteID key .EndpointID , alpn string , side Side , stableID uint64 ) (*Conn , error ) {
return &Conn {qc : qc , remoteID : remoteID , alpn : alpn , side : side , stableID : stableID }, nil
}
type Incoming struct {
ep *Endpoint
qc *quic .Conn
remote net .Addr
}
func (in *Incoming ) Accept () (*Accepting , error ) {
if in == nil || in .qc == nil {
return nil , errors .New ("iroh: nil incoming connection" )
}
return &Accepting {ep : in .ep , qc : in .qc }, nil
}
func (in *Incoming ) Refuse () {
if in != nil && in .qc != nil {
in .qc .CloseWithError (0 , "refused" )
}
}
func (in *Incoming ) Ignore () {
if in != nil && in .qc != nil {
in .qc .CloseWithError (0 , "" )
}
}
func (in *Incoming ) RemoteAddr () net .Addr {
if in == nil {
return nil
}
if in .remote != nil {
return in .remote
}
if in .qc == nil {
return nil
}
return in .qc .RemoteAddr ()
}
func (in *Incoming ) RemoteAddrValidated () bool {
if in == nil {
return false
}
if in .qc == nil {
return false
}
return in .qc .RemoteAddrValidated ()
}
func (in *Incoming ) LocalAddr () net .Addr {
if in == nil || in .qc == nil {
return nil
}
return in .qc .LocalAddr ()
}
type Accepting struct {
ep *Endpoint
qc *quic .Conn
}
func (a *Accepting ) ALPN (ctx context .Context ) (string , error ) {
if a == nil || a .qc == nil {
return "" , errors .New ("iroh: nil accepting connection" )
}
select {
case <- a .qc .HandshakeComplete ():
return a .qc .ConnectionState ().TLS .NegotiatedProtocol , nil
default :
}
select {
case <- a .qc .HandshakeComplete ():
case <- a .qc .Context ().Done ():
return "" , fmt .Errorf ("%w: %w" , ErrConnClosedDuringHandshake , context .Cause (a .qc .Context ()))
case <- ctx .Done ():
a .qc .CloseWithError (0 , "" )
return "" , ctx .Err ()
}
return a .qc .ConnectionState ().TLS .NegotiatedProtocol , nil
}
func (a *Accepting ) RemoteAddr () net .Addr {
if a == nil || a .qc == nil {
return nil
}
return a .qc .RemoteAddr ()
}
func (a *Accepting ) Connection (ctx context .Context ) (*Conn , error ) {
if a == nil || a .qc == nil {
return nil , errors .New ("iroh: nil accepting connection" )
}
return a .ep .finishAccepting (ctx , a .qc )
}
func (c *Conn ) RemoteID () key .EndpointID {
c .resolveIdentity ()
return c .remoteID
}
func (c *Conn ) ALPN () string {
c .resolveIdentity ()
return c .alpn
}
func (c *Conn ) Side () Side { return c .side }
func (c *Conn ) StableID () uint64 { return c .stableID }
func (c *Conn ) Stats () ConnStats {
return connStats (c .qc .ConnectionStats ())
}
func connStats(s quic .ConnectionStats ) ConnStats {
return ConnStats {
MinRTT : s .MinRTT ,
LatestRTT : s .LatestRTT ,
SmoothedRTT : s .SmoothedRTT ,
MeanDeviation : s .MeanDeviation ,
BytesSent : s .BytesSent ,
PacketsSent : s .PacketsSent ,
BytesReceived : s .BytesReceived ,
PacketsReceived : s .PacketsReceived ,
BytesLost : s .BytesLost ,
PacketsLost : s .PacketsLost ,
}
}
func (c *Conn ) Paths () []PathInfo {
c .resolveIdentity ()
if c .pathState != nil && c .pathConn != nil {
return pathInfosFromSocket (c .pathState .PathInfos (c .pathConn ))
}
return pathInfosFromSocket ((&connAdapter {qc : c .qc }).Paths ())
}
func (c *Conn ) WatchPaths (ctx context .Context ) (<-chan []PathInfo , error ) {
c .resolveIdentity ()
if c .pathState == nil || c .pathConn == nil {
return nil , errors .New ("iroh: path observation not available" )
}
events , cancel := c .pathState .PathEvents ()
out := make (chan []PathInfo , 1 )
go func () {
defer cancel ()
defer close (out )
send := func () bool {
paths := c .Paths ()
select {
case out <- paths :
return true
case <- ctx .Done ():
return false
case <- c .Context ().Done ():
return false
}
}
if !send () {
return
}
for {
select {
case <- ctx .Done ():
return
case <- c .Context ().Done ():
return
case _ , ok := <- events :
if !ok {
return
}
if !send () {
return
}
}
}
}()
return out , nil
}
func pathInfosFromSocket(paths []socket .PathInfo ) []PathInfo {
if len (paths ) == 0 {
return nil
}
out := make ([]PathInfo , 0 , len (paths ))
for _ , p := range paths {
info := PathInfo {
ID : p .ID ,
Validated : p .Validated ,
HasAddr : p .HasAddr ,
RTT : p .RTT ,
HasRTT : p .HasRTT ,
BytesInFlight : p .BytesInFlight ,
HasBytesInFlight : p .HasBytesInFlight ,
BytesSent : p .BytesSent ,
HasBytesSent : p .HasBytesSent ,
BytesReceived : p .BytesReceived ,
HasBytesReceived : p .HasBytesReceived ,
CongestionWindow : p .CongestionWindow ,
HasCongestionWindow : p .HasCongestionWindow ,
LostPackets : p .LostPackets ,
LostBytes : p .LostBytes ,
HasLoss : p .HasLoss ,
Selected : p .Selected ,
}
if p .HasAddr {
info .Addr , info .Relayed = transportAddrFromSocket (p .Addr )
}
out = append (out , info )
}
return out
}
func transportAddrFromSocket(addr socket .Addr ) (netaddr .TransportAddr , bool ) {
switch addr .Kind () {
case socket .AddrIP :
ap , _ := addr .IP ()
return netaddr .IPAddr {Addr : ap }, false
case socket .AddrRelay :
u , _ , _ := addr .Relay ()
return netaddr .RelayAddr {URL : u }, true
case socket .AddrCustom :
c , _ := addr .Custom ()
return c , false
default :
return nil , false
}
}
func (c *Conn ) OpenStreamSync (ctx context .Context ) (*Stream , error ) {
s , err := c .qc .OpenStreamSync (ctx )
if err != nil {
return nil , err
}
return &Stream {s : s }, nil
}
func (c *Conn ) OpenStreamConn (ctx context .Context ) (net .Conn , error ) {
s , err := c .OpenStreamSync (ctx )
if err != nil {
return nil , err
}
return streamConn {
Stream : s ,
local : c .LocalAddr (),
remote : c .RemoteAddr (),
remoteID : c .RemoteID (),
used0RTT : c .Used0RTT (),
}, nil
}
func (c *Conn ) AcceptStream (ctx context .Context ) (*Stream , error ) {
s , err := c .qc .AcceptStream (ctx )
if err != nil {
return nil , err
}
return &Stream {s : s }, nil
}
func (c *Conn ) AcceptStreamConn (ctx context .Context ) (net .Conn , error ) {
s , err := c .AcceptStream (ctx )
if err != nil {
return nil , err
}
return streamConn {
Stream : s ,
local : c .LocalAddr (),
remote : c .RemoteAddr (),
remoteID : c .RemoteID (),
used0RTT : c .Used0RTT (),
}, nil
}
func (c *Conn ) OpenUniStreamSync (ctx context .Context ) (*SendStream , error ) {
s , err := c .qc .OpenUniStreamSync (ctx )
if err != nil {
return nil , err
}
return &SendStream {s : s }, nil
}
func (c *Conn ) AcceptUniStream (ctx context .Context ) (*ReceiveStream , error ) {
s , err := c .qc .AcceptUniStream (ctx )
if err != nil {
return nil , err
}
return &ReceiveStream {s : s }, nil
}
func (c *Conn ) SendDatagram (b []byte ) error { return c .qc .SendDatagram (b ) }
func (c *Conn ) MaxDatagramSize () (n int , ok bool ) {
size , ok := c .qc .MaxDatagramSize ()
return int (size ), ok
}
func (c *Conn ) ReadDatagram (ctx context .Context ) ([]byte , error ) {
return c .qc .ReceiveDatagram (ctx )
}
func (c *Conn ) Used0RTT () bool { return c .qc .ConnectionState ().Used0RTT }
func (c *Conn ) MultipathNegotiated () bool {
return c .qc .ConnectionState ().MultipathNegotiated
}
func (c *Conn ) KeyExchangeGroup () string {
return c .qc .ConnectionState ().TLS .CurveID .String ()
}
func (c *Conn ) HandshakeComplete () <-chan struct {} { return c .qc .HandshakeComplete () }
func (c *Conn ) Context () context .Context { return c .qc .Context () }
func (c *Conn ) LocalAddr () net .Addr { return c .qc .LocalAddr () }
func (c *Conn ) RemoteAddr () net .Addr { return c .qc .RemoteAddr () }
func (c *Conn ) CloseWithError (code uint64 , reason string ) error {
return c .qc .CloseWithError (quic .ApplicationErrorCode (code ), reason )
}
func (c *Conn ) Close () error {
return c .CloseWithError (0 , "" )
}
type streamConn struct {
*Stream
local net .Addr
remote net .Addr
remoteID key .EndpointID
used0RTT bool
}
func (c streamConn ) LocalAddr () net .Addr { return c .local }
func (c streamConn ) RemoteAddr () net .Addr { return c .remote }
func (c streamConn ) RemoteID () key .EndpointID { return c .remoteID }
func (c streamConn ) Used0RTT () bool { return c .used0RTT }
func (c streamConn ) Close () error {
err := c .Stream .Close ()
c .Stream .CancelRead (0 )
if err != nil {
var serr *quic .StreamError
if errors .As (context .Cause (c .Stream .Context ()), &serr ) {
return nil
}
}
return err
}
type connAdapter struct {
qc *quic .Conn
addr socket .Addr
}
func newConnAdapter(qc *quic .Conn , addr socket .Addr ) *connAdapter {
return &connAdapter {qc : qc , addr : addr }
}
func (a *connAdapter ) SmoothedRTT () time .Duration { return a .qc .ConnectionStats ().SmoothedRTT }
func (a *connAdapter ) Done () <-chan struct {} { return a .qc .Context ().Done () }
func (a *connAdapter ) RemoteAddr () socket .Addr { return a .addr }
func (a *connAdapter ) MultipathNegotiated () bool {
return a .qc .ConnectionState ().MultipathNegotiated
}
func (a *connAdapter ) Paths () []socket .PathInfo {
qpaths := a .qc .Paths ()
if len (qpaths ) == 0 {
return nil
}
paths := make ([]socket .PathInfo , len (qpaths ))
for i , p := range qpaths {
paths [i ] = socket .PathInfo {
ID : uint32 (p .ID ),
Validated : p .Validated ,
}
if p .HasRTT {
paths [i ].RTT = p .SmoothedRTT
paths [i ].HasRTT = true
}
if p .HasBytesInFlight {
paths [i ].BytesInFlight = uint64 (p .BytesInFlight )
paths [i ].HasBytesInFlight = true
}
if p .HasBytesSent {
paths [i ].BytesSent = p .BytesSent
paths [i ].HasBytesSent = true
}
if p .HasBytesReceived {
paths [i ].BytesReceived = p .BytesReceived
paths [i ].HasBytesReceived = true
}
if p .HasCongestionWindow {
paths [i ].CongestionWindow = uint64 (p .CongestionWindow )
paths [i ].HasCongestionWindow = true
}
if p .HasLoss {
paths [i ].LostPackets = p .LostPackets
paths [i ].LostBytes = p .LostBytes
paths [i ].HasLoss = true
}
if p .RemoteAddr .IsValid () {
paths [i ].Addr = socket .IPAddr (p .RemoteAddr )
paths [i ].HasAddr = true
}
}
return paths
}
func (a *connAdapter ) AddNATTraversalAddress (addr netip .AddrPort ) error {
err := a .qc .AddNATTraversalAddress (addr )
if errors .Is (err , quic .ErrNATTraversalNotNegotiated ) {
return socket .ErrExtensionNotNegotiated
}
return err
}
func (a *connAdapter ) RemoveNATTraversalAddress (addr netip .AddrPort ) error {
err := a .qc .RemoveNATTraversalAddress (addr )
if errors .Is (err , quic .ErrNATTraversalNotNegotiated ) {
return socket .ErrExtensionNotNegotiated
}
return err
}
func (a *connAdapter ) InitiateNATTraversalRound (ctx context .Context ) ([]netip .AddrPort , error ) {
addrs , err := a .qc .InitiateNATTraversalRound (ctx )
if errors .Is (err , quic .ErrNATTraversalNotNegotiated ) {
return nil , socket .ErrExtensionNotNegotiated
}
return addrs , err
}
func (a *connAdapter ) NATTraversalAddresses () ([]netip .AddrPort , error ) {
addrs , err := a .qc .NATTraversalAddresses ()
if errors .Is (err , quic .ErrNATTraversalNotNegotiated ) {
return nil , socket .ErrExtensionNotNegotiated
}
return addrs , err
}
func (a *connAdapter ) AddRemoteNATTraversalAddress (addr netip .AddrPort ) error {
err := a .qc .AddRemoteNATTraversalAddress (addr )
if errors .Is (err , quic .ErrNATTraversalNotNegotiated ) {
return socket .ErrExtensionNotNegotiated
}
return err
}
func (a *connAdapter ) OpenPath (ctx context .Context ) error {
for {
p , err := a .qc .OpenPath (nil )
if err == nil {
return p .Validated (ctx )
}
if !errors .Is (err , quic .ErrPathLimit ) {
return err
}
t := time .NewTimer (10 * time .Millisecond )
select {
case <- t .C :
case <- ctx .Done ():
t .Stop ()
return context .Cause (ctx )
}
}
}
var _ socket .Connection = (*connAdapter )(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 .