265 lines
7.5 KiB
Go
Raw Normal View History

2021-05-23 14:42:26 -05:00
package core
import (
"crypto/ed25519"
2019-01-31 23:29:18 +00:00
"encoding/hex"
2019-01-05 12:06:45 +00:00
"errors"
"fmt"
"io"
"net"
"net/url"
"strings"
2019-01-04 17:23:37 +00:00
"sync"
2019-03-04 17:09:48 +00:00
//"sync/atomic"
2019-01-05 12:06:45 +00:00
"time"
2019-01-04 17:23:37 +00:00
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
2020-05-09 11:24:32 +01:00
"golang.org/x/net/proxy"
//"github.com/Arceliar/phony" // TODO? use instead of mutexes
)
2021-05-23 14:33:28 -05:00
type keyArray [ed25519.PublicKeySize]byte
2020-05-23 10:23:55 -05:00
type links struct {
2020-05-23 10:28:57 -05:00
core *Core
mutex sync.RWMutex // protects links below
links map[linkInfo]*link
tcp tcp // TCP interface support
stopped chan struct{}
// TODO timeout (to remove from switch), read from config.ReadTimeout
}
// linkInfo is used as a map key
type linkInfo struct {
2021-05-23 14:33:28 -05:00
key keyArray
linkType string // Type of link, e.g. TCP, AWDL
local string // Local name or address
remote string // Remote name or address
}
2020-05-23 10:28:57 -05:00
type link struct {
lname string
links *links
conn net.Conn
options linkOptions
info linkInfo
incoming bool
force bool
closed chan struct{}
}
type linkOptions struct {
2021-05-23 14:33:28 -05:00
pinnedEd25519Keys map[keyArray]struct{}
}
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
l.mutex.Lock()
2020-05-23 10:28:57 -05:00
l.links = make(map[linkInfo]*link)
2019-01-04 17:23:37 +00:00
l.mutex.Unlock()
l.stopped = make(chan struct{})
2019-03-04 17:09:48 +00:00
if err := l.tcp.init(l); err != nil {
c.log.Errorln("Failed to start TCP interface")
return err
}
2019-01-04 17:23:37 +00:00
return nil
}
func (l *links) call(u *url.URL, sintf string) error {
//u, err := url.Parse(uri)
//if err != nil {
// return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err)
//}
tcpOpts := tcpOptions{}
if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 {
2021-05-23 14:33:28 -05:00
tcpOpts.pinnedEd25519Keys = make(map[keyArray]struct{})
for _, pubkey := range pubkeys {
2020-05-09 12:49:02 +01:00
if sigPub, err := hex.DecodeString(pubkey); err == nil {
2021-05-23 14:33:28 -05:00
var sigPubKey keyArray
copy(sigPubKey[:], sigPub)
2020-05-09 12:38:20 +01:00
tcpOpts.pinnedEd25519Keys[sigPubKey] = struct{}{}
}
}
}
switch u.Scheme {
case "tcp":
l.tcp.call(u.Host, tcpOpts, sintf)
case "socks":
tcpOpts.socksProxyAddr = u.Host
2020-05-09 11:24:32 +01:00
if u.User != nil {
tcpOpts.socksProxyAuth = &proxy.Auth{}
tcpOpts.socksProxyAuth.User = u.User.Username()
tcpOpts.socksProxyAuth.Password, _ = u.User.Password()
}
2021-05-23 21:47:12 -05:00
pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/")
l.tcp.call(pathtokens[0], tcpOpts, sintf)
case "tls":
tcpOpts.upgrade = l.tcp.tls.forDialer
l.tcp.call(u.Host, tcpOpts, sintf)
default:
return errors.New("unknown call scheme: " + u.Scheme)
}
return nil
}
func (l *links) create(conn net.Conn, name, linkType, local, remote string, incoming, force bool, options linkOptions) (*link, error) {
2019-11-29 11:45:02 +02:00
// Technically anything unique would work for names, but let's pick something human readable, just for debugging
2020-05-23 10:28:57 -05:00
intf := link{
conn: conn,
2020-05-23 11:33:37 -05:00
lname: name,
links: l,
options: options,
info: linkInfo{
linkType: linkType,
local: local,
remote: remote,
},
incoming: incoming,
force: force,
2019-01-04 17:23:37 +00:00
}
return &intf, nil
}
2020-05-23 10:23:55 -05:00
func (l *links) stop() error {
close(l.stopped)
2019-09-18 16:32:22 +01:00
if err := l.tcp.stop(); err != nil {
return err
}
return nil
}
func (intf *link) handler() (chan struct{}, error) {
// TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later
defer intf.conn.Close()
meta := version_getBaseMetadata()
meta.key = intf.links.core.public
metaBytes := meta.encode()
// TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer)
2019-02-26 21:07:56 -06:00
var err error
if !util.FuncTimeout(30*time.Second, func() {
var n int
n, err = intf.conn.Write(metaBytes)
if err == nil && n != len(metaBytes) {
err = errors.New("incomplete metadata send")
}
}) {
return nil, errors.New("timeout on metadata send")
2019-02-26 21:07:56 -06:00
}
if err != nil {
return nil, err
}
if !util.FuncTimeout(30*time.Second, func() {
var n int
n, err = io.ReadFull(intf.conn, metaBytes)
if err == nil && n != len(metaBytes) {
err = errors.New("incomplete metadata recv")
}
}) {
return nil, errors.New("timeout on metadata recv")
2019-02-26 21:07:56 -06:00
}
if err != nil {
return nil, err
}
meta = version_metadata{}
2021-05-10 22:31:01 +01:00
base := version_getBaseMetadata()
if !meta.decode(metaBytes) {
return nil, errors.New("failed to decode metadata")
}
2021-05-10 22:31:01 +01:00
if !meta.check() {
intf.links.core.log.Errorf("Failed to connect to node: %s is incompatible version (local %s, remote %s)",
intf.lname,
fmt.Sprintf("%d.%d", base.ver, base.minorVer),
fmt.Sprintf("%d.%d", meta.ver, meta.minorVer),
)
return nil, errors.New("remote node is incompatible version")
}
// 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.
2020-05-09 12:38:20 +01:00
if pinned := intf.options.pinnedEd25519Keys; pinned != nil {
2021-05-23 14:33:28 -05:00
var key keyArray
copy(key[:], meta.key)
if _, allowed := pinned[key]; !allowed {
2021-05-29 11:13:59 -05:00
intf.links.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name())
return nil, fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys")
}
}
2019-01-31 23:29:18 +00:00
// Check if we're authorized to connect to this key / IP
intf.links.core.config.RLock()
allowed := intf.links.core.config.AllowedPublicKeys
intf.links.core.config.RUnlock()
2021-05-10 22:39:12 +01:00
isallowed := len(allowed) == 0
for _, k := range allowed {
if k == hex.EncodeToString(meta.key) { // TODO: this is yuck
isallowed = true
break
}
}
if intf.incoming && !intf.force && !isallowed {
2020-05-23 10:23:55 -05:00
intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s",
2021-05-10 22:39:12 +01:00
strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.key))
intf.close()
return nil, nil
2019-01-31 23:29:18 +00:00
}
// Check if we already have a link to this node
copy(intf.info.key[:], meta.key)
2020-05-23 10:23:55 -05:00
intf.links.mutex.Lock()
if oldIntf, isIn := intf.links.links[intf.info]; isIn {
2020-05-23 10:23:55 -05:00
intf.links.mutex.Unlock()
// FIXME we should really return an error and let the caller block instead
// That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc.
2021-05-29 11:13:59 -05:00
intf.links.core.log.Debugln("DEBUG: found existing interface for", intf.name())
return oldIntf.closed, nil
} else {
intf.closed = make(chan struct{})
2020-05-23 10:28:57 -05:00
intf.links.links[intf.info] = intf
2019-01-22 21:48:43 -06:00
defer func() {
2020-05-23 10:23:55 -05:00
intf.links.mutex.Lock()
2020-05-23 10:28:57 -05:00
delete(intf.links.links, intf.info)
2020-05-23 10:23:55 -05:00
intf.links.mutex.Unlock()
2019-08-25 22:55:17 -05:00
close(intf.closed)
2019-01-22 21:48:43 -06:00
}()
2021-05-29 11:13:59 -05:00
intf.links.core.log.Debugln("DEBUG: registered interface for", intf.name())
}
2020-05-23 10:23:55 -05:00
intf.links.mutex.Unlock()
themAddr := address.AddrForKey(ed25519.PublicKey(intf.info.key[:]))
themAddrString := net.IP(themAddr[:]).String()
themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote)
2020-05-23 10:23:55 -05:00
intf.links.core.log.Infof("Connected %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
// Run the handler
err = intf.links.core.PacketConn.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn)
// TODO don't report an error if it's just a 'use of closed network connection'
2019-08-25 22:55:17 -05:00
if err != nil {
2020-05-23 10:23:55 -05:00
intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local, err)
2019-08-25 22:55:17 -05:00
} else {
2020-05-23 10:23:55 -05:00
intf.links.core.log.Infof("Disconnected %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
}
return nil, err
}
2020-05-23 10:28:57 -05:00
func (intf *link) close() {
intf.conn.Close()
2020-05-16 17:07:47 -05:00
}
2020-05-23 10:28:57 -05:00
func (intf *link) name() string {
2020-05-16 17:07:47 -05:00
return intf.lname
}
2020-05-23 10:28:57 -05:00
func (intf *link) local() string {
2020-05-16 17:07:47 -05:00
return intf.info.local
}
2020-05-23 10:28:57 -05:00
func (intf *link) remote() string {
2020-05-16 17:07:47 -05:00
return intf.info.remote
}
2020-05-23 10:28:57 -05:00
func (intf *link) interfaceType() string {
2020-05-16 17:07:47 -05:00
return intf.info.linkType
}