2021-05-23 14:42:26 -05:00
package core
2019-01-04 17:14:40 +00:00
import (
2022-07-24 10:23:25 +01:00
"bytes"
2023-04-06 21:45:49 +01:00
"context"
2019-01-31 23:29:18 +00:00
"encoding/hex"
2024-08-11 10:42:25 +01:00
"errors"
2019-01-05 12:06:45 +00:00
"fmt"
2019-02-24 14:48:16 -06:00
"io"
2019-01-31 23:18:02 +00:00
"net"
2019-03-04 22:45:35 +00:00
"net/url"
2022-10-26 09:24:24 +01:00
"strconv"
2019-01-31 23:18:02 +00:00
"strings"
2022-02-01 13:37:45 +00:00
"sync/atomic"
2022-10-02 13:20:39 +01:00
"time"
2022-02-01 13:37:45 +00:00
2022-08-06 15:05:12 +01:00
"github.com/Arceliar/phony"
2021-05-15 13:44:55 -05:00
"github.com/yggdrasil-network/yggdrasil-go/src/address"
2023-10-09 16:44:07 +01:00
"golang.org/x/crypto/blake2b"
2019-01-04 17:14:40 +00:00
)
2023-04-06 21:45:49 +01:00
type linkType int
const (
linkTypePersistent linkType = iota // Statically configured
linkTypeEphemeral // Multicast discovered
linkTypeIncoming // Incoming connection
)
2024-01-15 23:09:07 +00:00
const defaultBackoffLimit = time . Second << 12 // 1h8m16s
const minimumBackoffLimit = time . Second * 30
2020-05-23 10:23:55 -05:00
type links struct {
2023-06-18 03:40:40 -05:00
phony . Inbox
core * Core
tcp * linkTCP // TCP interface support
tls * linkTLS // TLS interface support
unix * linkUNIX // UNIX interface support
socks * linkSOCKS // SOCKS interface support
2023-05-21 11:29:05 +01:00
quic * linkQUIC // QUIC interface support
2024-07-23 21:58:11 +00:00
ws * linkWS // WS interface support
wss * linkWSS // WSS interface support
2023-06-18 03:40:40 -05:00
// _links can only be modified safely from within the links actor
2024-08-11 10:42:25 +01:00
_links map [ linkInfo ] * link // *link is nil if connection in progress
_listeners map [ * Listener ] context . CancelFunc
2019-01-22 21:16:41 -06:00
}
2023-04-06 21:45:49 +01:00
type linkProtocol interface {
2023-08-12 18:12:58 +01:00
dial ( ctx context . Context , url * url . URL , info linkInfo , options linkOptions ) ( net . Conn , error )
2023-04-06 21:45:49 +01:00
listen ( ctx context . Context , url * url . URL , sintf string ) ( net . Listener , error )
2019-01-21 21:27:52 -06:00
}
2023-04-06 21:45:49 +01:00
// linkInfo is used as a map key
type linkInfo struct {
2023-05-20 23:44:31 +01:00
uri string // Peering URI in complete form
sintf string // Peering source interface (i.e. from InterfacePeers)
2022-11-12 11:30:03 +00:00
}
2023-04-06 21:45:49 +01:00
// link tracks the state of a connection, either persistent or non-persistent
2020-05-23 10:28:57 -05:00
type link struct {
2023-10-22 10:27:41 +01:00
ctx context . Context // Connection context
cancel context . CancelFunc // Stop future redial attempts (when peer removed)
kick chan struct { } // Attempt to reconnect now, if backing off
linkType linkType // Type of link, i.e. outbound/inbound, persistent/ephemeral
linkProto string // Protocol carrier of link, e.g. TCP, AWDL
2023-06-18 03:40:40 -05:00
// The remaining fields can only be modified safely from within the links actor
_conn * linkConn // Connected link, if any, nil if not connected
_err error // Last error on the connection, if any
2024-05-27 22:57:28 +02:00
_errtime time . Time // Last time an error occurred
2019-01-04 17:14:40 +00:00
}
2020-05-08 23:23:48 +01:00
type linkOptions struct {
2021-05-23 14:33:28 -05:00
pinnedEd25519Keys map [ keyArray ] struct { }
2022-10-26 09:24:24 +01:00
priority uint8
2023-04-06 21:45:49 +01:00
tlsSNI string
2023-10-09 16:44:07 +01:00
password [ ] byte
2024-01-15 23:09:07 +00:00
maxBackoff time . Duration
2020-05-08 23:23:48 +01:00
}
2022-09-17 20:07:00 +01:00
type Listener struct {
2023-04-06 21:45:49 +01:00
listener net . Listener
ctx context . Context
Cancel context . CancelFunc
}
func ( l * Listener ) Addr ( ) net . Addr {
return l . listener . Addr ( )
2022-09-17 20:07:00 +01:00
}
2020-05-23 10:23:55 -05:00
func ( l * links ) init ( c * Core ) error {
2019-01-04 17:23:37 +00:00
l . core = c
2022-09-17 20:07:00 +01:00
l . tcp = l . newLinkTCP ( )
l . tls = l . newLinkTLS ( l . tcp )
l . unix = l . newLinkUNIX ( )
l . socks = l . newLinkSOCKS ( )
2023-05-21 11:29:05 +01:00
l . quic = l . newLinkQUIC ( )
2024-07-23 21:58:11 +00:00
l . ws = l . newLinkWS ( )
l . wss = l . newLinkWSS ( )
2023-05-20 18:36:44 +01:00
l . _links = make ( map [ linkInfo ] * link )
2024-08-11 10:42:25 +01:00
l . _listeners = make ( map [ * Listener ] context . CancelFunc )
2019-03-04 17:09:48 +00:00
2019-01-04 17:23:37 +00:00
return nil
2019-01-04 17:14:40 +00:00
}
2022-09-24 17:05:44 +01:00
func ( l * links ) shutdown ( ) {
2024-08-11 10:42:25 +01:00
phony . Block ( l , func ( ) {
for listener := range l . _listeners {
_ = listener . listener . Close ( )
2022-09-17 20:07:00 +01:00
}
2024-08-11 10:42:25 +01:00
for _ , link := range l . _links {
2024-09-22 17:05:25 +01:00
if link . _conn != nil {
_ = link . _conn . Close ( )
}
2022-09-17 20:07:00 +01:00
}
} )
}
2023-04-06 21:45:49 +01:00
type linkError string
func ( e linkError ) Error ( ) string { return string ( e ) }
const ErrLinkAlreadyConfigured = linkError ( "peer is already configured" )
2023-10-22 10:27:41 +01:00
const ErrLinkNotConfigured = linkError ( "peer is not configured" )
2023-04-06 21:45:49 +01:00
const ErrLinkPriorityInvalid = linkError ( "priority value is invalid" )
const ErrLinkPinnedKeyInvalid = linkError ( "pinned public key is invalid" )
2024-08-01 21:53:48 +01:00
const ErrLinkPasswordInvalid = linkError ( "invalid password supplied" )
2023-04-06 21:45:49 +01:00
const ErrLinkUnrecognisedSchema = linkError ( "link schema unknown" )
2024-01-15 23:09:07 +00:00
const ErrLinkMaxBackoffInvalid = linkError ( "max backoff duration invalid" )
2023-04-06 21:45:49 +01:00
func ( l * links ) add ( u * url . URL , sintf string , linkType linkType ) error {
2023-06-18 03:40:40 -05:00
var retErr error
phony . Block ( l , func ( ) {
// Generate the link info and see whether we think we already
// have an open peering to this peer.
lu := urlForLinkInfo ( * u )
info := linkInfo {
uri : lu . String ( ) ,
sintf : sintf ,
2020-05-09 00:43:19 +01:00
}
2023-06-18 03:40:40 -05:00
// Collect together the link options, these are global options
// that are not specific to any given protocol.
2024-01-15 23:09:07 +00:00
options := linkOptions {
maxBackoff : defaultBackoffLimit ,
}
2023-06-18 03:40:40 -05:00
for _ , pubkey := range u . Query ( ) [ "key" ] {
sigPub , err := hex . DecodeString ( pubkey )
if err != nil {
retErr = ErrLinkPinnedKeyInvalid
return
}
var sigPubKey keyArray
copy ( sigPubKey [ : ] , sigPub )
if options . pinnedEd25519Keys == nil {
options . pinnedEd25519Keys = map [ keyArray ] struct { } { }
}
options . pinnedEd25519Keys [ sigPubKey ] = struct { } { }
2023-04-06 21:45:49 +01:00
}
2023-06-18 03:40:40 -05:00
if p := u . Query ( ) . Get ( "priority" ) ; p != "" {
pi , err := strconv . ParseUint ( p , 10 , 8 )
if err != nil {
retErr = ErrLinkPriorityInvalid
return
}
options . priority = uint8 ( pi )
2022-10-26 09:24:24 +01:00
}
2023-10-09 16:44:07 +01:00
if p := u . Query ( ) . Get ( "password" ) ; p != "" {
if len ( p ) > blake2b . Size {
retErr = ErrLinkPasswordInvalid
return
}
options . password = [ ] byte ( p )
}
2024-01-15 23:09:07 +00:00
if p := u . Query ( ) . Get ( "maxbackoff" ) ; p != "" {
d , err := time . ParseDuration ( p )
if err != nil || d < minimumBackoffLimit {
retErr = ErrLinkMaxBackoffInvalid
return
}
options . maxBackoff = d
}
2023-11-04 17:57:15 +00:00
// SNI headers must contain hostnames and not IP addresses, so we must make sure
// that we do not populate the SNI with an IP literal. We do this by splitting
// the host-port combo from the query option and then seeing if it parses to an
// IP address successfully or not.
if sni := u . Query ( ) . Get ( "sni" ) ; sni != "" {
if net . ParseIP ( sni ) == nil {
options . tlsSNI = sni
}
}
// If the SNI is not configured still because the above failed then we'll try
// again but this time we'll use the host part of the peering URI instead.
if options . tlsSNI == "" {
if host , _ , err := net . SplitHostPort ( u . Host ) ; err == nil && net . ParseIP ( host ) == nil {
options . tlsSNI = host
}
}
2023-04-06 21:45:49 +01:00
2023-06-18 03:40:40 -05:00
// If we think we're already connected to this peer, load up
// the existing peer state. Try to kick the peer if possible,
// which will cause an immediate connection attempt if it is
// backing off for some reason.
state , ok := l . _links [ info ]
if ok && state != nil {
select {
case state . kick <- struct { } { } :
default :
}
retErr = ErrLinkAlreadyConfigured
return
2023-05-21 00:02:04 +01:00
}
2023-06-18 03:40:40 -05:00
// Create the link entry. This will contain the connection
// in progress (if any), any error details and a context that
// lets the link be cancelled later.
state = & link {
linkType : linkType ,
linkProto : strings . ToUpper ( u . Scheme ) ,
kick : make ( chan struct { } ) ,
}
2023-10-22 10:27:41 +01:00
state . ctx , state . cancel = context . WithCancel ( l . core . ctx )
2023-05-21 00:02:04 +01:00
2023-06-18 03:40:40 -05:00
// Store the state of the link so that it can be queried later.
l . _links [ info ] = state
// Track how many consecutive connection failures we have had,
// as we will back off exponentially rather than hammering the
// remote node endlessly.
var backoff int
// backoffNow is called when there's a connection error. It
// will wait for the specified amount of time and then return
// true, unless the peering context was cancelled (due to a
// peer removal most likely), in which case it returns false.
// The caller should check the return value to decide whether
// or not to give up trying.
backoffNow := func ( ) bool {
2024-01-15 23:09:07 +00:00
if backoff < 32 {
2023-11-21 23:54:27 +00:00
backoff ++
}
2024-01-15 23:09:07 +00:00
duration := time . Second << backoff
if duration > options . maxBackoff {
duration = options . maxBackoff
}
2023-06-18 03:40:40 -05:00
select {
case <- state . kick :
return true
2023-10-22 10:27:41 +01:00
case <- state . ctx . Done ( ) :
return false
2023-06-18 03:40:40 -05:00
case <- l . core . ctx . Done ( ) :
return false
2023-10-22 10:27:41 +01:00
case <- time . After ( duration ) :
return true
2023-06-18 03:40:40 -05:00
}
2023-04-06 21:45:49 +01:00
}
2023-11-21 10:35:17 +00:00
// resetBackoff is called by the connection handler when the
// handshake has successfully completed.
resetBackoff := func ( ) {
backoff = 0
}
2023-06-18 03:40:40 -05:00
// The goroutine is responsible for attempting the connection
// and then running the handler. If the connection is persistent
// then the loop will run endlessly, using backoffs as needed.
// Otherwise the loop will end, cleaning up the link entry.
go func ( ) {
2023-10-22 10:27:41 +01:00
defer phony . Block ( l , func ( ) {
if l . _links [ info ] == state {
delete ( l . _links , info )
}
} )
2023-06-18 03:40:40 -05:00
// This loop will run each and every time we want to attempt
// a connection to this peer.
// TODO get rid of this loop, this is *exactly* what time.AfterFunc is for, we should just send a signal to the links actor to kick off a goroutine as needed
for {
2023-10-22 10:27:41 +01:00
select {
case <- state . ctx . Done ( ) :
// The peering context has been cancelled, so don't try
// to dial again.
return
default :
}
conn , err := l . connect ( state . ctx , u , info , options )
2023-11-04 18:42:42 +00:00
if err != nil || conn == nil {
if err == nil && conn == nil {
l . core . log . Warnf ( "Link %q reached inconsistent error state" , u . String ( ) )
}
2023-06-18 03:40:40 -05:00
if linkType == linkTypePersistent {
// If the link is a persistent configured peering,
// store information about the connection error so
// that we can report it through the admin socket.
phony . Block ( l , func ( ) {
state . _conn = nil
state . _err = err
state . _errtime = time . Now ( )
} )
// Back off for a bit. If true is returned here, we
// can continue onto the next loop iteration to try
// the next connection.
if backoffNow ( ) {
continue
}
2023-09-03 13:49:21 +01:00
return
2023-06-18 03:40:40 -05:00
}
2023-09-03 13:49:21 +01:00
// Ephemeral and incoming connections don't remain
// after a connection failure, so exit out of the
// loop and clean up the link entry.
break
2023-06-18 03:40:40 -05:00
}
2023-05-20 23:44:31 +01:00
2023-06-18 03:40:40 -05:00
// The linkConn wrapper allows us to track the number of
// bytes written to and read from this connection without
// the help of ironwood.
lc := & linkConn {
Conn : conn ,
up : time . Now ( ) ,
}
// Update the link state with our newly wrapped connection.
// Clear the error state.
var doRet bool
phony . Block ( l , func ( ) {
if state . _conn != nil {
// If a peering has come up in this time, abort this one.
doRet = true
}
state . _conn = lc
} )
if doRet {
return
}
// Give the connection to the handler. The handler will block
// for the lifetime of the connection.
2023-11-21 10:35:17 +00:00
if err = l . handler ( linkType , options , lc , resetBackoff ) ; err != nil && err != io . EOF {
2023-06-18 03:40:40 -05:00
l . core . log . Debugf ( "Link %s error: %s\n" , info . uri , err )
}
// The handler has stopped running so the connection is dead,
// try to close the underlying socket just in case and then
// update the link state.
_ = lc . Close ( )
phony . Block ( l , func ( ) {
2023-05-20 23:44:31 +01:00
state . _conn = nil
2024-08-01 21:53:48 +01:00
if err == nil {
err = fmt . Errorf ( "remote side closed the connection" )
2023-06-18 03:40:40 -05:00
}
2024-08-01 21:53:48 +01:00
state . _err = err
state . _errtime = time . Now ( )
2023-06-18 03:40:40 -05:00
} )
2023-05-20 23:44:31 +01:00
2023-06-18 03:40:40 -05:00
// If the link is persistently configured, back off if needed
// and then try reconnecting. Otherwise, exit out.
if linkType == linkTypePersistent {
2023-04-06 21:45:49 +01:00
if backoffNow ( ) {
continue
}
}
2024-09-29 21:23:45 +01:00
// Ephemeral or incoming connections don't reconnect.
return
2022-11-12 11:30:03 +00:00
}
2023-06-18 03:40:40 -05:00
} ( )
} )
return retErr
2023-04-06 21:45:49 +01:00
}
2022-09-17 20:07:00 +01:00
2024-07-20 12:31:58 +01:00
func ( l * links ) remove ( u * url . URL , sintf string , _ linkType ) error {
2023-10-22 10:27:41 +01:00
var retErr error
phony . Block ( l , func ( ) {
// Generate the link info and see whether we think we already
// have an open peering to this peer.
lu := urlForLinkInfo ( * u )
info := linkInfo {
uri : lu . String ( ) ,
sintf : sintf ,
}
// If this peer is already configured then we will close the
// connection and stop it from retrying.
state , ok := l . _links [ info ]
if ok && state != nil {
state . cancel ( )
if conn := state . _conn ; conn != nil {
retErr = conn . Close ( )
}
return
}
retErr = ErrLinkNotConfigured
} )
return retErr
}
2023-04-06 21:45:49 +01:00
func ( l * links ) listen ( u * url . URL , sintf string ) ( * Listener , error ) {
ctx , cancel := context . WithCancel ( l . core . ctx )
var protocol linkProtocol
switch strings . ToLower ( u . Scheme ) {
case "tcp" :
protocol = l . tcp
case "tls" :
protocol = l . tls
case "unix" :
protocol = l . unix
2023-05-21 11:29:05 +01:00
case "quic" :
protocol = l . quic
2024-07-23 21:58:11 +00:00
case "ws" :
protocol = l . ws
case "wss" :
protocol = l . wss
2023-04-06 21:45:49 +01:00
default :
cancel ( )
return nil , ErrLinkUnrecognisedSchema
}
listener , err := protocol . listen ( ctx , u , sintf )
if err != nil {
cancel ( )
return nil , err
}
li := & Listener {
listener : listener ,
ctx : ctx ,
2024-09-29 20:58:10 +01:00
Cancel : func ( ) {
cancel ( )
_ = listener . Close ( )
} ,
2023-04-06 21:45:49 +01:00
}
2023-05-20 23:44:31 +01:00
var options linkOptions
if p := u . Query ( ) . Get ( "priority" ) ; p != "" {
pi , err := strconv . ParseUint ( p , 10 , 8 )
if err != nil {
return nil , ErrLinkPriorityInvalid
}
options . priority = uint8 ( pi )
}
2023-10-09 16:44:07 +01:00
if p := u . Query ( ) . Get ( "password" ) ; p != "" {
if len ( p ) > blake2b . Size {
return nil , ErrLinkPasswordInvalid
}
options . password = [ ] byte ( p )
}
2023-05-20 23:44:31 +01:00
2024-08-11 10:42:25 +01:00
phony . Block ( l , func ( ) {
l . _listeners [ li ] = cancel
} )
2023-04-06 21:45:49 +01:00
go func ( ) {
2024-08-11 10:42:25 +01:00
l . core . log . Infof ( "%s listener started on %s" , strings . ToUpper ( u . Scheme ) , li . listener . Addr ( ) )
defer l . core . log . Infof ( "%s listener stopped on %s" , strings . ToUpper ( u . Scheme ) , li . listener . Addr ( ) )
defer phony . Block ( l , func ( ) {
delete ( l . _listeners , li )
} )
2023-04-06 21:45:49 +01:00
for {
2024-08-11 10:42:25 +01:00
conn , err := li . listener . Accept ( )
2023-04-06 21:45:49 +01:00
if err != nil {
2023-10-17 21:41:21 +01:00
return
2023-04-06 21:45:49 +01:00
}
2023-05-20 22:22:15 +01:00
go func ( conn net . Conn ) {
defer conn . Close ( )
2023-05-20 23:44:31 +01:00
// In order to populate a somewhat sane looking connection
// URI in the admin socket, we need to replace the host in
// the listener URL with the remote address.
2023-05-20 22:22:15 +01:00
pu := * u
pu . Host = conn . RemoteAddr ( ) . String ( )
lu := urlForLinkInfo ( pu )
info := linkInfo {
2023-05-20 23:44:31 +01:00
uri : lu . String ( ) ,
sintf : sintf ,
2022-11-12 11:30:03 +00:00
}
2023-05-20 23:44:31 +01:00
2023-05-21 00:02:04 +01:00
// If there's an existing link state for this link, get it.
2023-05-20 23:44:31 +01:00
// If this node is already connected to us, just drop the
// connection. This prevents duplicate peerings.
2023-06-18 03:40:40 -05:00
var lc * linkConn
var state * link
phony . Block ( l , func ( ) {
var ok bool
state , ok = l . _links [ info ]
if ! ok || state == nil {
state = & link {
linkType : linkTypeIncoming ,
linkProto : strings . ToUpper ( u . Scheme ) ,
kick : make ( chan struct { } ) ,
}
}
if state . _conn != nil {
// If a connection has come up in this time, abort
// this one.
return
2023-05-20 22:22:15 +01:00
}
2023-05-20 23:44:31 +01:00
2023-06-18 03:40:40 -05:00
// The linkConn wrapper allows us to track the number of
// bytes written to and read from this connection without
// the help of ironwood.
lc = & linkConn {
Conn : conn ,
up : time . Now ( ) ,
}
2023-05-20 23:44:31 +01:00
2023-06-18 03:40:40 -05:00
// Update the link state with our newly wrapped connection.
// Clear the error state.
state . _conn = lc
state . _err = nil
state . _errtime = time . Time { }
2023-05-20 23:44:31 +01:00
2023-06-18 03:40:40 -05:00
// Store the state of the link so that it can be queried later.
l . _links [ info ] = state
} )
2024-08-11 10:42:25 +01:00
defer phony . Block ( l , func ( ) {
if l . _links [ info ] == state {
delete ( l . _links , info )
}
} )
2023-06-18 03:40:40 -05:00
if lc == nil {
return
}
2023-05-20 23:44:31 +01:00
// Give the connection to the handler. The handler will block
// for the lifetime of the connection.
2024-08-11 10:42:25 +01:00
switch err = l . handler ( linkTypeIncoming , options , lc , nil ) ; {
case err == nil :
case errors . Is ( err , io . EOF ) :
case errors . Is ( err , net . ErrClosed ) :
default :
2023-05-20 22:22:15 +01:00
l . core . log . Debugf ( "Link %s error: %s\n" , u . Host , err )
}
2023-05-20 23:44:31 +01:00
// The handler has stopped running so the connection is dead,
// try to close the underlying socket just in case and then
// drop the link state.
_ = lc . Close ( )
2023-05-20 22:22:15 +01:00
} ( conn )
2023-04-06 21:45:49 +01:00
}
} ( )
return li , nil
}
2022-09-17 20:07:00 +01:00
2023-10-22 10:27:41 +01:00
func ( l * links ) connect ( ctx context . Context , u * url . URL , info linkInfo , options linkOptions ) ( net . Conn , error ) {
2023-04-06 21:45:49 +01:00
var dialer linkProtocol
switch strings . ToLower ( u . Scheme ) {
case "tcp" :
dialer = l . tcp
2019-10-23 17:26:35 +01:00
case "tls" :
2023-04-06 21:45:49 +01:00
dialer = l . tls
2023-11-04 17:57:15 +00:00
case "socks" , "sockstls" :
2023-04-06 21:45:49 +01:00
dialer = l . socks
2022-09-17 20:07:00 +01:00
case "unix" :
2023-04-06 21:45:49 +01:00
dialer = l . unix
2023-05-21 11:29:05 +01:00
case "quic" :
dialer = l . quic
2024-07-23 21:58:11 +00:00
case "ws" :
dialer = l . ws
case "wss" :
dialer = l . wss
2022-09-17 20:07:00 +01:00
default :
2023-04-06 21:45:49 +01:00
return nil , ErrLinkUnrecognisedSchema
2019-01-04 17:23:37 +00:00
}
2023-10-22 10:27:41 +01:00
return dialer . dial ( ctx , u , info , options )
2019-01-19 12:19:24 +00:00
}
2023-11-21 10:35:17 +00:00
func ( l * links ) handler ( linkType linkType , options linkOptions , conn net . Conn , success func ( ) ) error {
2019-01-21 23:08:50 -06:00
meta := version_getBaseMetadata ( )
2023-04-06 21:45:49 +01:00
meta . publicKey = l . core . public
2023-07-15 22:34:29 +01:00
meta . priority = options . priority
2023-10-09 16:44:07 +01:00
metaBytes , err := meta . encode ( l . core . secret , options . password )
if err != nil {
return fmt . Errorf ( "failed to generate handshake: %w" , err )
}
2023-04-06 21:45:49 +01:00
if err := conn . SetDeadline ( time . Now ( ) . Add ( time . Second * 6 ) ) ; err != nil {
2022-09-24 16:51:31 +01:00
return fmt . Errorf ( "failed to set handshake deadline: %w" , err )
2019-02-26 21:07:56 -06:00
}
2023-04-06 21:45:49 +01:00
n , err := conn . Write ( metaBytes )
2022-09-24 16:51:31 +01:00
switch {
case err != nil :
2022-09-17 20:07:00 +01:00
return fmt . Errorf ( "write handshake: %w" , err )
2024-07-20 12:31:58 +01:00
case n != len ( metaBytes ) :
2022-09-24 16:51:31 +01:00
return fmt . Errorf ( "incomplete handshake send" )
2019-01-21 23:08:50 -06:00
}
meta = version_metadata { }
2021-05-10 22:31:01 +01:00
base := version_getBaseMetadata ( )
2023-11-04 17:57:04 +00:00
if err := meta . decode ( conn , options . password ) ; err != nil {
_ = conn . Close ( )
return err
2019-01-21 23:08:50 -06:00
}
2021-05-10 22:31:01 +01:00
if ! meta . check ( ) {
2023-04-06 21:45:49 +01:00
return fmt . Errorf ( "remote node incompatible version (local %s, remote %s)" ,
2023-03-18 12:14:32 +00:00
fmt . Sprintf ( "%d.%d" , base . majorVer , base . minorVer ) ,
fmt . Sprintf ( "%d.%d" , meta . majorVer , meta . minorVer ) ,
2021-05-10 22:31:01 +01:00
)
2019-01-21 23:08:50 -06:00
}
2023-06-18 20:28:14 +01:00
if err = conn . SetDeadline ( time . Time { } ) ; err != nil {
return fmt . Errorf ( "failed to clear handshake deadline: %w" , err )
}
2020-05-08 23:23:48 +01:00
// Check if the remote side matches the keys we expected. This is a bit of a weak
// check - in future versions we really should check a signature or something like that.
2023-04-06 21:45:49 +01:00
if pinned := options . pinnedEd25519Keys ; len ( pinned ) > 0 {
2021-05-23 14:33:28 -05:00
var key keyArray
2023-03-18 12:14:32 +00:00
copy ( key [ : ] , meta . publicKey )
2021-05-08 08:35:58 -05:00
if _ , allowed := pinned [ key ] ; ! allowed {
2022-10-22 16:23:25 +01:00
return fmt . Errorf ( "node public key that does not match pinned keys" )
2020-05-08 23:23:48 +01:00
}
}
2019-01-31 23:29:18 +00:00
// Check if we're authorized to connect to this key / IP
2023-04-06 21:45:49 +01:00
var allowed map [ [ 32 ] byte ] struct { }
phony . Block ( l . core , func ( ) {
allowed = l . core . config . _allowedPublicKeys
} )
2021-05-10 22:39:12 +01:00
isallowed := len ( allowed ) == 0
2022-07-24 10:23:25 +01:00
for k := range allowed {
2023-03-18 12:14:32 +00:00
if bytes . Equal ( k [ : ] , meta . publicKey ) {
2021-05-10 22:39:12 +01:00
isallowed = true
break
}
}
2023-05-20 23:44:31 +01:00
if linkType == linkTypeIncoming && ! isallowed {
2023-03-18 12:14:32 +00:00
return fmt . Errorf ( "node public key %q is not in AllowedPublicKeys" , hex . EncodeToString ( meta . publicKey ) )
2019-01-31 23:29:18 +00:00
}
2022-09-17 20:07:00 +01:00
2022-10-22 16:23:25 +01:00
dir := "outbound"
2023-05-20 23:44:31 +01:00
if linkType == linkTypeIncoming {
2022-10-22 16:23:25 +01:00
dir = "inbound"
}
2023-03-18 12:14:32 +00:00
remoteAddr := net . IP ( address . AddrForKey ( meta . publicKey ) [ : ] ) . String ( )
2023-04-06 21:45:49 +01:00
remoteStr := fmt . Sprintf ( "%s@%s" , remoteAddr , conn . RemoteAddr ( ) )
localStr := conn . LocalAddr ( )
2023-07-15 22:34:29 +01:00
priority := options . priority
if meta . priority > priority {
priority = meta . priority
}
2023-04-06 21:45:49 +01:00
l . core . log . Infof ( "Connected %s: %s, source %s" ,
dir , remoteStr , localStr )
2023-11-21 10:35:17 +00:00
if success != nil {
success ( )
}
2022-09-17 20:07:00 +01:00
2023-07-15 22:34:29 +01:00
err = l . core . HandleConn ( meta . publicKey , conn , priority )
2022-10-22 14:56:11 +01:00
switch err {
case io . EOF , net . ErrClosed , nil :
2023-04-06 21:45:49 +01:00
l . core . log . Infof ( "Disconnected %s: %s, source %s" ,
dir , remoteStr , localStr )
2022-10-22 14:56:11 +01:00
default :
2023-04-06 21:45:49 +01:00
l . core . log . Infof ( "Disconnected %s: %s, source %s; error: %s" ,
dir , remoteStr , localStr , err )
2019-02-24 14:48:16 -06:00
}
2024-08-01 21:53:48 +01:00
return err
2019-01-21 23:08:50 -06:00
}
2019-08-25 22:19:20 -05:00
2023-05-20 21:18:49 +01:00
func urlForLinkInfo ( u url . URL ) url . URL {
u . RawQuery = ""
return u
}
2022-02-01 13:37:45 +00:00
type linkConn struct {
// tx and rx are at the beginning of the struct to ensure 64-bit alignment
// on 32-bit platforms, see https://pkg.go.dev/sync/atomic#pkg-note-BUG
rx uint64
tx uint64
up time . Time
net . Conn
}
func ( c * linkConn ) Read ( p [ ] byte ) ( n int , err error ) {
n , err = c . Conn . Read ( p )
atomic . AddUint64 ( & c . rx , uint64 ( n ) )
return
}
func ( c * linkConn ) Write ( p [ ] byte ) ( n int , err error ) {
n , err = c . Conn . Write ( p )
atomic . AddUint64 ( & c . tx , uint64 ( n ) )
return
}