2021-05-23 19:42:26 +00:00
|
|
|
package core
|
2019-01-04 17:14:40 +00:00
|
|
|
|
|
|
|
import (
|
2022-07-24 09:23:25 +00:00
|
|
|
"bytes"
|
2019-01-31 23:29:18 +00:00
|
|
|
"encoding/hex"
|
2019-01-05 12:06:45 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2019-02-24 20:48:16 +00:00
|
|
|
"io"
|
2019-01-31 23:18:02 +00:00
|
|
|
"net"
|
2019-03-04 22:45:35 +00:00
|
|
|
"net/url"
|
2019-01-31 23:18:02 +00:00
|
|
|
"strings"
|
2019-03-04 17:09:48 +00:00
|
|
|
|
2019-01-22 05:08:50 +00:00
|
|
|
//"sync/atomic"
|
2019-01-05 12:06:45 +00:00
|
|
|
"time"
|
2019-01-04 17:23:37 +00:00
|
|
|
|
2022-02-01 13:37:45 +00:00
|
|
|
"sync/atomic"
|
|
|
|
|
2022-08-06 14:05:12 +00:00
|
|
|
"github.com/Arceliar/phony"
|
2021-05-15 18:44:55 +00:00
|
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
2021-05-08 13:35:58 +00:00
|
|
|
//"github.com/Arceliar/phony" // TODO? use instead of mutexes
|
2019-01-04 17:14:40 +00:00
|
|
|
)
|
|
|
|
|
2020-05-23 15:23:55 +00:00
|
|
|
type links struct {
|
2022-09-17 19:07:00 +00: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
|
|
|
|
_links map[linkInfo]*link // *link is nil if connection in progress
|
2019-02-03 04:18:55 +00:00
|
|
|
// TODO timeout (to remove from switch), read from config.ReadTimeout
|
2019-01-23 03:16:41 +00:00
|
|
|
}
|
|
|
|
|
2021-05-08 13:35:58 +00:00
|
|
|
// linkInfo is used as a map key
|
2019-01-23 03:16:41 +00:00
|
|
|
type linkInfo struct {
|
2021-05-08 13:35:58 +00:00
|
|
|
linkType string // Type of link, e.g. TCP, AWDL
|
|
|
|
local string // Local name or address
|
|
|
|
remote string // Remote name or address
|
2019-01-22 03:27:52 +00:00
|
|
|
}
|
|
|
|
|
2020-05-23 15:28:57 +00:00
|
|
|
type link struct {
|
2021-05-08 13:35:58 +00:00
|
|
|
lname string
|
|
|
|
links *links
|
2022-02-01 13:37:45 +00:00
|
|
|
conn *linkConn
|
2021-05-08 13:35:58 +00:00
|
|
|
options linkOptions
|
|
|
|
info linkInfo
|
|
|
|
incoming bool
|
|
|
|
force bool
|
2019-01-04 17:14:40 +00:00
|
|
|
}
|
|
|
|
|
2020-05-08 22:23:48 +00:00
|
|
|
type linkOptions struct {
|
2021-05-23 19:33:28 +00:00
|
|
|
pinnedEd25519Keys map[keyArray]struct{}
|
2020-05-08 22:23:48 +00:00
|
|
|
}
|
|
|
|
|
2022-09-17 19:07:00 +00:00
|
|
|
type Listener struct {
|
|
|
|
net.Listener
|
|
|
|
closed chan struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Listener) Close() error {
|
|
|
|
err := l.Listener.Close()
|
|
|
|
<-l.closed
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-05-23 15:23:55 +00:00
|
|
|
func (l *links) init(c *Core) error {
|
2019-01-04 17:23:37 +00:00
|
|
|
l.core = c
|
2022-09-17 19:07:00 +00:00
|
|
|
l.tcp = l.newLinkTCP()
|
|
|
|
l.tls = l.newLinkTLS(l.tcp)
|
|
|
|
l.unix = l.newLinkUNIX()
|
|
|
|
l.socks = l.newLinkSOCKS()
|
|
|
|
l._links = make(map[linkInfo]*link)
|
2019-01-04 17:14:40 +00:00
|
|
|
|
2022-08-06 14:05:12 +00:00
|
|
|
var listeners []ListenAddress
|
|
|
|
phony.Block(c, func() {
|
|
|
|
listeners = make([]ListenAddress, 0, len(c.config._listeners))
|
|
|
|
for listener := range c.config._listeners {
|
|
|
|
listeners = append(listeners, listener)
|
|
|
|
}
|
|
|
|
})
|
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-17 19:07:00 +00:00
|
|
|
func (l *links) shutdown() error {
|
|
|
|
phony.Block(l.tcp, func() {
|
|
|
|
for l := range l.tcp._listeners {
|
|
|
|
l.Close()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
phony.Block(l.tls, func() {
|
|
|
|
for l := range l.tls._listeners {
|
|
|
|
l.Close()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
phony.Block(l.unix, func() {
|
|
|
|
for l := range l.unix._listeners {
|
|
|
|
l.Close()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *links) isConnectedTo(info linkInfo) bool {
|
|
|
|
var isConnected bool
|
|
|
|
phony.Block(l, func() {
|
|
|
|
_, isConnected = l._links[info]
|
|
|
|
})
|
|
|
|
return isConnected
|
|
|
|
}
|
|
|
|
|
2021-05-24 01:34:13 +00:00
|
|
|
func (l *links) call(u *url.URL, sintf string) error {
|
2022-09-17 19:07:00 +00:00
|
|
|
info := linkInfoFor(u.Scheme, sintf, u.Host)
|
|
|
|
if l.isConnectedTo(info) {
|
2022-09-24 12:46:22 +00:00
|
|
|
return nil
|
2022-09-17 19:07:00 +00:00
|
|
|
}
|
|
|
|
options := linkOptions{
|
|
|
|
pinnedEd25519Keys: map[keyArray]struct{}{},
|
|
|
|
}
|
|
|
|
for _, pubkey := range u.Query()["key"] {
|
2022-09-24 12:46:22 +00:00
|
|
|
sigPub, err := hex.DecodeString(pubkey)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("pinned key contains invalid hex characters")
|
2020-05-08 23:43:19 +00:00
|
|
|
}
|
2022-09-24 12:46:22 +00:00
|
|
|
var sigPubKey keyArray
|
|
|
|
copy(sigPubKey[:], sigPub)
|
|
|
|
options.pinnedEd25519Keys[sigPubKey] = struct{}{}
|
2020-05-08 22:23:48 +00:00
|
|
|
}
|
2022-09-17 19:07:00 +00:00
|
|
|
switch info.linkType {
|
2019-03-04 22:45:35 +00:00
|
|
|
case "tcp":
|
2022-09-17 19:07:00 +00:00
|
|
|
go func() {
|
|
|
|
if err := l.tcp.dial(u, options, sintf); err != nil {
|
|
|
|
l.core.log.Warnf("Failed to dial TCP %s: %s\n", u.Host, err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2019-03-04 22:45:35 +00:00
|
|
|
case "socks":
|
2022-09-17 19:07:00 +00:00
|
|
|
go func() {
|
|
|
|
if err := l.socks.dial(u, options); err != nil {
|
|
|
|
l.core.log.Warnf("Failed to dial SOCKS %s: %s\n", u.Host, err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2019-10-23 16:26:35 +00:00
|
|
|
case "tls":
|
2021-08-01 20:36:51 +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.
|
2022-09-17 19:07:00 +00:00
|
|
|
var tlsSNI string
|
2021-08-01 20:36:51 +00:00
|
|
|
if sni := u.Query().Get("sni"); sni != "" {
|
2021-08-01 20:39:49 +00:00
|
|
|
if net.ParseIP(sni) == nil {
|
2022-09-17 19:07:00 +00:00
|
|
|
tlsSNI = sni
|
2021-08-01 20:36:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// 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.
|
2022-09-17 19:07:00 +00:00
|
|
|
if tlsSNI == "" {
|
2021-07-28 21:23:33 +00:00
|
|
|
if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil {
|
2022-09-17 19:07:00 +00:00
|
|
|
tlsSNI = host
|
2021-07-28 21:23:33 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-17 19:07:00 +00:00
|
|
|
go func() {
|
|
|
|
if err := l.tls.dial(u, options, sintf, tlsSNI); err != nil {
|
|
|
|
l.core.log.Warnf("Failed to dial TLS %s: %s\n", u.Host, err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
case "unix":
|
|
|
|
go func() {
|
|
|
|
if err := l.unix.dial(u, options, sintf); err != nil {
|
|
|
|
l.core.log.Warnf("Failed to dial UNIX %s: %s\n", u.Host, err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2019-03-04 22:45:35 +00:00
|
|
|
default:
|
|
|
|
return errors.New("unknown call scheme: " + u.Scheme)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-09-17 19:07:00 +00:00
|
|
|
func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
|
|
|
|
var listener *Listener
|
|
|
|
var err error
|
|
|
|
switch u.Scheme {
|
|
|
|
case "tcp":
|
|
|
|
listener, err = l.tcp.listen(u, sintf)
|
|
|
|
case "tls":
|
|
|
|
listener, err = l.tls.listen(u, sintf)
|
|
|
|
case "unix":
|
|
|
|
listener, err = l.unix.listen(u, sintf)
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme)
|
|
|
|
}
|
|
|
|
return listener, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *links) create(conn net.Conn, name string, info linkInfo, incoming, force bool, options linkOptions) error {
|
2020-05-23 15:28:57 +00:00
|
|
|
intf := link{
|
2022-02-01 13:37:45 +00:00
|
|
|
conn: &linkConn{
|
|
|
|
Conn: conn,
|
|
|
|
up: time.Now(),
|
|
|
|
},
|
2022-09-17 19:07:00 +00:00
|
|
|
lname: name,
|
|
|
|
links: l,
|
|
|
|
options: options,
|
|
|
|
info: info,
|
2019-02-01 00:02:17 +00:00
|
|
|
incoming: incoming,
|
|
|
|
force: force,
|
2019-01-04 17:23:37 +00:00
|
|
|
}
|
2022-09-17 19:07:00 +00:00
|
|
|
go func() {
|
|
|
|
if err := intf.handler(); err != nil {
|
|
|
|
l.core.log.Errorf("Link handler %s error (%s): %s", name, conn.RemoteAddr(), err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return nil
|
2019-01-19 12:19:24 +00:00
|
|
|
}
|
|
|
|
|
2022-09-17 19:07:00 +00:00
|
|
|
func (intf *link) handler() error {
|
|
|
|
defer intf.conn.Close()
|
|
|
|
|
|
|
|
// Don't connect to this link more than once.
|
|
|
|
if intf.links.isConnectedTo(intf.info) {
|
|
|
|
return fmt.Errorf("already connected to this node")
|
2019-09-18 15:32:22 +00:00
|
|
|
}
|
|
|
|
|
2022-09-17 19:07:00 +00:00
|
|
|
// Mark the connection as in progress.
|
|
|
|
phony.Block(intf.links, func() {
|
|
|
|
intf.links._links[intf.info] = nil
|
|
|
|
})
|
|
|
|
|
|
|
|
// When we're done, clean up the connection entry.
|
|
|
|
defer phony.Block(intf.links, func() {
|
|
|
|
delete(intf.links._links, intf.info)
|
|
|
|
})
|
|
|
|
|
2019-01-22 05:08:50 +00:00
|
|
|
meta := version_getBaseMetadata()
|
2021-05-08 13:35:58 +00:00
|
|
|
meta.key = intf.links.core.public
|
2019-01-22 05:08:50 +00:00
|
|
|
metaBytes := meta.encode()
|
2022-09-24 15:51:31 +00:00
|
|
|
if err := intf.conn.SetDeadline(time.Now().Add(time.Second * 6)); err != nil {
|
|
|
|
return fmt.Errorf("failed to set handshake deadline: %w", err)
|
2019-02-27 03:07:56 +00:00
|
|
|
}
|
2022-09-24 15:51:31 +00:00
|
|
|
n, err := intf.conn.Write(metaBytes)
|
|
|
|
switch {
|
|
|
|
case err != nil:
|
2022-09-17 19:07:00 +00:00
|
|
|
return fmt.Errorf("write handshake: %w", err)
|
2022-09-24 15:51:31 +00:00
|
|
|
case err == nil && n != len(metaBytes):
|
|
|
|
return fmt.Errorf("incomplete handshake send")
|
2019-01-22 05:08:50 +00:00
|
|
|
}
|
2022-09-24 15:51:31 +00:00
|
|
|
if _, err = io.ReadFull(intf.conn, metaBytes); err != nil {
|
2022-09-17 19:07:00 +00:00
|
|
|
return fmt.Errorf("read handshake: %w", err)
|
2019-01-22 05:08:50 +00:00
|
|
|
}
|
2022-09-24 15:51:31 +00:00
|
|
|
if err := intf.conn.SetDeadline(time.Time{}); err != nil {
|
|
|
|
return fmt.Errorf("failed to clear handshake deadline: %w", err)
|
|
|
|
}
|
2019-01-22 05:08:50 +00:00
|
|
|
meta = version_metadata{}
|
2021-05-10 21:31:01 +00:00
|
|
|
base := version_getBaseMetadata()
|
|
|
|
if !meta.decode(metaBytes) {
|
2022-09-17 19:07:00 +00:00
|
|
|
return errors.New("failed to decode metadata")
|
2019-01-22 05:08:50 +00:00
|
|
|
}
|
2021-05-10 21:31:01 +00:00
|
|
|
if !meta.check() {
|
2022-01-30 21:58:57 +00:00
|
|
|
var connectError string
|
|
|
|
if intf.incoming {
|
|
|
|
connectError = "Rejected incoming connection"
|
|
|
|
} else {
|
|
|
|
connectError = "Failed to connect"
|
|
|
|
}
|
|
|
|
intf.links.core.log.Debugf("%s: %s is incompatible version (local %s, remote %s)",
|
|
|
|
connectError,
|
2021-05-10 21:31:01 +00:00
|
|
|
intf.lname,
|
|
|
|
fmt.Sprintf("%d.%d", base.ver, base.minorVer),
|
|
|
|
fmt.Sprintf("%d.%d", meta.ver, meta.minorVer),
|
|
|
|
)
|
2022-09-17 19:07:00 +00:00
|
|
|
return errors.New("remote node is incompatible version")
|
2019-01-22 05:08:50 +00:00
|
|
|
}
|
2020-05-08 22:23:48 +00: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.
|
2022-09-17 19:07:00 +00:00
|
|
|
if pinned := intf.options.pinnedEd25519Keys; len(pinned) > 0 {
|
2021-05-23 19:33:28 +00:00
|
|
|
var key keyArray
|
2021-05-08 13:35:58 +00:00
|
|
|
copy(key[:], meta.key)
|
|
|
|
if _, allowed := pinned[key]; !allowed {
|
2021-05-29 16:13:59 +00:00
|
|
|
intf.links.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name())
|
2022-09-17 19:07:00 +00:00
|
|
|
return fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys")
|
2020-05-08 22:23:48 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-31 23:29:18 +00:00
|
|
|
// Check if we're authorized to connect to this key / IP
|
2022-07-24 09:23:25 +00:00
|
|
|
allowed := intf.links.core.config._allowedPublicKeys
|
2021-05-10 21:39:12 +00:00
|
|
|
isallowed := len(allowed) == 0
|
2022-07-24 09:23:25 +00:00
|
|
|
for k := range allowed {
|
|
|
|
if bytes.Equal(k[:], meta.key) {
|
2021-05-10 21:39:12 +00:00
|
|
|
isallowed = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if intf.incoming && !intf.force && !isallowed {
|
2020-05-23 15:23:55 +00:00
|
|
|
intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s",
|
2021-05-10 21:39:12 +00:00
|
|
|
strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.key))
|
|
|
|
intf.close()
|
2022-09-17 19:07:00 +00:00
|
|
|
return fmt.Errorf("forbidden connection")
|
2019-01-31 23:29:18 +00:00
|
|
|
}
|
2022-09-17 19:07:00 +00:00
|
|
|
|
|
|
|
phony.Block(intf.links, func() {
|
|
|
|
intf.links._links[intf.info] = intf
|
|
|
|
})
|
|
|
|
|
|
|
|
remoteAddr := net.IP(address.AddrForKey(meta.key)[:]).String()
|
|
|
|
remoteStr := fmt.Sprintf("%s@%s", remoteAddr, intf.info.remote)
|
|
|
|
localStr := intf.conn.LocalAddr()
|
2020-05-23 15:23:55 +00:00
|
|
|
intf.links.core.log.Infof("Connected %s: %s, source %s",
|
2022-09-17 19:07:00 +00:00
|
|
|
strings.ToUpper(intf.info.linkType), remoteStr, localStr)
|
|
|
|
|
2019-09-20 00:15:59 +00:00
|
|
|
// TODO don't report an error if it's just a 'use of closed network connection'
|
2022-09-17 19:07:00 +00:00
|
|
|
if err = intf.links.core.HandleConn(meta.key, intf.conn); err != nil && err != io.EOF {
|
2020-05-23 15:23:55 +00:00
|
|
|
intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s",
|
2022-09-17 19:07:00 +00:00
|
|
|
strings.ToUpper(intf.info.linkType), remoteStr, localStr, err)
|
2019-08-26 03:55:17 +00:00
|
|
|
} else {
|
2020-05-23 15:23:55 +00:00
|
|
|
intf.links.core.log.Infof("Disconnected %s: %s, source %s",
|
2022-09-17 19:07:00 +00:00
|
|
|
strings.ToUpper(intf.info.linkType), remoteStr, localStr)
|
2019-02-24 20:48:16 +00:00
|
|
|
}
|
2022-09-17 19:07:00 +00:00
|
|
|
|
|
|
|
return nil
|
2019-01-22 05:08:50 +00:00
|
|
|
}
|
2019-08-26 03:19:20 +00:00
|
|
|
|
2022-09-17 19:07:00 +00:00
|
|
|
func (intf *link) close() error {
|
|
|
|
return intf.conn.Close()
|
2020-05-16 22:07:47 +00:00
|
|
|
}
|
|
|
|
|
2020-05-23 15:28:57 +00:00
|
|
|
func (intf *link) name() string {
|
2020-05-16 22:07:47 +00:00
|
|
|
return intf.lname
|
|
|
|
}
|
2022-02-01 13:37:45 +00:00
|
|
|
|
2022-09-17 19:07:00 +00:00
|
|
|
func linkInfoFor(linkType, sintf, remote string) linkInfo {
|
|
|
|
if h, _, err := net.SplitHostPort(remote); err == nil {
|
|
|
|
remote = h
|
|
|
|
}
|
|
|
|
return linkInfo{
|
|
|
|
linkType: linkType,
|
|
|
|
local: sintf,
|
|
|
|
remote: remote,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|