yggdrasil-go/src/yggdrasil/peer.go

374 lines
11 KiB
Go
Raw Normal View History

2017-12-29 04:16:20 +00:00
package yggdrasil
// TODO cleanup, this file is kind of a mess
// Commented code should be removed
// Live code should be better commented
2017-12-29 04:16:20 +00:00
2018-06-12 22:50:08 +00:00
import (
"encoding/hex"
2018-06-12 22:50:08 +00:00
"sync"
"sync/atomic"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
2018-06-12 22:50:08 +00:00
)
2017-12-29 04:16:20 +00:00
// The peers struct represents peers with an active connection.
2019-01-09 09:42:07 +00:00
// Incoming packets are passed to the corresponding peer, which handles them somehow.
// In most cases, this involves passing the packet to the handler for outgoing traffic to another peer.
// In other cases, it's link protocol traffic used to build the spanning tree, in which case this checks signatures and passes the message along to the switch.
2017-12-29 04:16:20 +00:00
type peers struct {
core *Core
reconfigure chan chan error
mutex sync.Mutex // Synchronize writes to atomic
ports atomic.Value //map[switchPort]*peer, use CoW semantics
2017-12-29 04:16:20 +00:00
}
// Initializes the peers struct.
2017-12-29 04:16:20 +00:00
func (ps *peers) init(c *Core) {
2018-01-04 22:37:51 +00:00
ps.mutex.Lock()
defer ps.mutex.Unlock()
ps.putPorts(make(map[switchPort]*peer))
ps.core = c
2018-12-30 12:04:42 +00:00
ps.reconfigure = make(chan chan error, 1)
go func() {
for {
2019-01-15 08:51:19 +00:00
e := <-ps.reconfigure
e <- nil
}
}()
}
// Returns true if an incoming peer connection to a key is allowed, either
// because the key is in the whitelist or because the whitelist is empty.
func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool {
boxstr := hex.EncodeToString(box[:])
ps.core.configMutex.RLock()
defer ps.core.configMutex.RUnlock()
for _, v := range ps.core.config.AllowedEncryptionPublicKeys {
if v == boxstr {
return true
}
}
return len(ps.core.config.AllowedEncryptionPublicKeys) == 0
2017-12-29 04:16:20 +00:00
}
// Adds a key to the whitelist.
func (ps *peers) addAllowedEncryptionPublicKey(box string) {
ps.core.configMutex.RLock()
defer ps.core.configMutex.RUnlock()
ps.core.config.AllowedEncryptionPublicKeys =
append(ps.core.config.AllowedEncryptionPublicKeys, box)
2018-05-07 00:01:52 +00:00
}
// Removes a key from the whitelist.
func (ps *peers) removeAllowedEncryptionPublicKey(box string) {
ps.core.configMutex.RLock()
defer ps.core.configMutex.RUnlock()
for k, v := range ps.core.config.AllowedEncryptionPublicKeys {
if v == box {
ps.core.config.AllowedEncryptionPublicKeys =
append(ps.core.config.AllowedEncryptionPublicKeys[:k],
ps.core.config.AllowedEncryptionPublicKeys[k+1:]...)
}
}
2018-05-07 00:01:52 +00:00
}
// Gets the whitelist of allowed keys for incoming connections.
func (ps *peers) getAllowedEncryptionPublicKeys() []string {
ps.core.configMutex.RLock()
defer ps.core.configMutex.RUnlock()
return ps.core.config.AllowedEncryptionPublicKeys
2018-05-07 00:01:52 +00:00
}
// Atomically gets a map[switchPort]*peer of known peers.
2017-12-29 04:16:20 +00:00
func (ps *peers) getPorts() map[switchPort]*peer {
2018-01-04 22:37:51 +00:00
return ps.ports.Load().(map[switchPort]*peer)
2017-12-29 04:16:20 +00:00
}
// Stores a map[switchPort]*peer (note that you should take a mutex before store operations to avoid conflicts with other nodes attempting to read/change/store at the same time).
2017-12-29 04:16:20 +00:00
func (ps *peers) putPorts(ports map[switchPort]*peer) {
2018-01-04 22:37:51 +00:00
ps.ports.Store(ports)
2017-12-29 04:16:20 +00:00
}
// Information known about a peer, including thier box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic
2017-12-29 04:16:20 +00:00
type peer struct {
bytesSent uint64 // To track bandwidth usage for getPeers
bytesRecvd uint64 // To track bandwidth usage for getPeers
2018-01-04 22:37:51 +00:00
// BUG: sync/atomic, 32 bit platforms need the above to be the first element
core *Core
port switchPort
box crypto.BoxPubKey
sig crypto.SigPubKey
shared crypto.BoxSharedKey
linkShared crypto.BoxSharedKey
endpoint string
firstSeen time.Time // To track uptime for getPeers
linkOut (chan []byte) // used for protocol traffic (to bypass queues)
doSend (chan struct{}) // tell the linkLoop to send a switchMsg
2018-12-15 23:57:36 +00:00
dinfo (chan *dhtInfo) // used to keep the DHT working
out func([]byte) // Set up by whatever created the peers struct, used to send packets to other nodes
close func() // Called when a peer is removed, to close the underlying connection, or via admin api
2017-12-29 04:16:20 +00:00
}
2018-01-04 22:37:51 +00:00
2019-01-09 09:42:07 +00:00
// Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unoccupied port number.
func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, endpoint string, closer func()) *peer {
now := time.Now()
2018-01-04 22:37:51 +00:00
p := peer{box: *box,
sig: *sig,
shared: *crypto.GetSharedKey(&ps.core.boxPriv, box),
linkShared: *linkShared,
endpoint: endpoint,
firstSeen: now,
doSend: make(chan struct{}, 1),
2018-12-15 03:44:31 +00:00
dinfo: make(chan *dhtInfo, 1),
close: closer,
core: ps.core}
2018-01-04 22:37:51 +00:00
ps.mutex.Lock()
defer ps.mutex.Unlock()
oldPorts := ps.getPorts()
newPorts := make(map[switchPort]*peer)
for k, v := range oldPorts {
newPorts[k] = v
}
for idx := switchPort(0); true; idx++ {
if _, isIn := newPorts[idx]; !isIn {
p.port = switchPort(idx)
newPorts[p.port] = &p
break
}
}
ps.putPorts(newPorts)
return &p
2017-12-29 04:16:20 +00:00
}
// Removes a peer for a given port, if one exists.
func (ps *peers) removePeer(port switchPort) {
if port == 0 {
return
} // Can't remove self peer
ps.core.router.doAdmin(func() {
ps.core.switchTable.forgetPeer(port)
})
ps.mutex.Lock()
oldPorts := ps.getPorts()
p, isIn := oldPorts[port]
newPorts := make(map[switchPort]*peer)
for k, v := range oldPorts {
newPorts[k] = v
}
delete(newPorts, port)
ps.putPorts(newPorts)
ps.mutex.Unlock()
if isIn {
if p.close != nil {
p.close()
}
close(p.doSend)
}
}
// If called, sends a notification to each peer that they should send a new switch message.
// Mainly called by the switch after an update.
func (ps *peers) sendSwitchMsgs() {
ports := ps.getPorts()
for _, p := range ports {
if p.port == 0 {
continue
}
p.doSendSwitchMsgs()
}
}
// If called, sends a notification to the peer's linkLoop to trigger a switchMsg send.
// Mainly called by sendSwitchMsgs or during linkLoop startup.
func (p *peer) doSendSwitchMsgs() {
defer func() { recover() }() // In case there's a race with close(p.doSend)
select {
case p.doSend <- struct{}{}:
default:
}
}
// This must be launched in a separate goroutine by whatever sets up the peer struct.
// It handles link protocol traffic.
func (p *peer) linkLoop() {
2018-06-07 15:58:24 +00:00
tick := time.NewTicker(time.Second)
defer tick.Stop()
p.doSendSwitchMsgs()
2018-12-15 03:44:31 +00:00
var dinfo *dhtInfo
2018-06-07 15:58:24 +00:00
for {
select {
case _, ok := <-p.doSend:
if !ok {
return
}
p.sendSwitchMsg()
2018-12-15 03:44:31 +00:00
case dinfo = <-p.dinfo:
2018-06-07 15:58:24 +00:00
case _ = <-tick.C:
if dinfo != nil {
p.core.dht.peers <- dinfo
2018-06-07 15:58:24 +00:00
}
}
2018-01-04 22:37:51 +00:00
}
2017-12-29 04:16:20 +00:00
}
// Called to handle incoming packets.
// Passes the packet to a handler for that packet type.
func (p *peer) handlePacket(packet []byte) {
2018-06-12 22:50:08 +00:00
// FIXME this is off by stream padding and msg length overhead, should be done in tcp.go
atomic.AddUint64(&p.bytesRecvd, uint64(len(packet)))
2018-01-04 22:37:51 +00:00
pType, pTypeLen := wire_decode_uint64(packet)
if pTypeLen == 0 {
return
}
switch pType {
case wire_Traffic:
p.handleTraffic(packet, pTypeLen)
case wire_ProtocolTraffic:
p.handleTraffic(packet, pTypeLen)
case wire_LinkProtocolTraffic:
p.handleLinkTraffic(packet)
default:
util.PutBytes(packet)
2018-01-04 22:37:51 +00:00
}
return
2017-12-29 04:16:20 +00:00
}
// Called to handle traffic or protocolTraffic packets.
// In either case, this reads from the coords of the packet header, does a switch lookup, and forwards to the next node.
2017-12-29 04:16:20 +00:00
func (p *peer) handleTraffic(packet []byte, pTypeLen int) {
2018-12-15 03:44:31 +00:00
table := p.core.switchTable.getTable()
if _, isIn := table.elems[p.port]; !isIn && p.port != 0 {
// Drop traffic if the peer isn't in the switch
return
}
p.core.switchTable.packetIn <- packet
2017-12-29 04:16:20 +00:00
}
// This just calls p.out(packet) for now.
2017-12-29 04:16:20 +00:00
func (p *peer) sendPacket(packet []byte) {
2018-01-04 22:37:51 +00:00
// Is there ever a case where something more complicated is needed?
// What if p.out blocks?
2019-01-23 17:05:16 +00:00
atomic.AddUint64(&p.bytesSent, uint64(len(packet)))
2018-01-04 22:37:51 +00:00
p.out(packet)
2017-12-29 04:16:20 +00:00
}
// This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers.
// It sends it to p.linkOut, which bypasses the usual packet queues.
2017-12-29 04:16:20 +00:00
func (p *peer) sendLinkPacket(packet []byte) {
innerPayload, innerNonce := crypto.BoxSeal(&p.linkShared, packet, nil)
innerLinkPacket := wire_linkProtoTrafficPacket{
Nonce: *innerNonce,
Payload: innerPayload,
}
outerPayload := innerLinkPacket.encode()
bs, nonce := crypto.BoxSeal(&p.shared, outerPayload, nil)
2018-01-04 22:37:51 +00:00
linkPacket := wire_linkProtoTrafficPacket{
2018-06-02 20:21:05 +00:00
Nonce: *nonce,
Payload: bs,
2018-01-04 22:37:51 +00:00
}
packet = linkPacket.encode()
p.linkOut <- packet
2017-12-29 04:16:20 +00:00
}
// Decrypts the outer (permanent) and inner (ephemeral) crypto layers on link traffic.
// Identifies the link traffic type and calls the appropriate handler.
2017-12-29 04:16:20 +00:00
func (p *peer) handleLinkTraffic(bs []byte) {
2018-01-04 22:37:51 +00:00
packet := wire_linkProtoTrafficPacket{}
if !packet.decode(bs) {
return
}
outerPayload, isOK := crypto.BoxOpen(&p.shared, packet.Payload, &packet.Nonce)
if !isOK {
return
}
innerPacket := wire_linkProtoTrafficPacket{}
if !innerPacket.decode(outerPayload) {
return
}
payload, isOK := crypto.BoxOpen(&p.linkShared, innerPacket.Payload, &innerPacket.Nonce)
2018-01-04 22:37:51 +00:00
if !isOK {
return
}
pType, pTypeLen := wire_decode_uint64(payload)
if pTypeLen == 0 {
return
}
switch pType {
case wire_SwitchMsg:
p.handleSwitchMsg(payload)
default:
util.PutBytes(bs)
2018-01-04 22:37:51 +00:00
}
}
// Gets a switchMsg from the switch, adds signed next-hop info for this peer, and sends it to them.
func (p *peer) sendSwitchMsg() {
msg := p.core.switchTable.getMsg()
if msg == nil {
return
}
bs := getBytesForSig(&p.sig, msg)
msg.Hops = append(msg.Hops, switchMsgHop{
Port: p.port,
Next: p.sig,
Sig: *crypto.Sign(&p.core.sigPriv, bs),
})
packet := msg.encode()
p.sendLinkPacket(packet)
}
// Handles a switchMsg from the peer, checking signatures and passing good messages to the switch.
// Also creates a dhtInfo struct and arranges for it to be added to the dht (this is how dht bootstrapping begins).
func (p *peer) handleSwitchMsg(packet []byte) {
var msg switchMsg
if !msg.decode(packet) {
return
}
if len(msg.Hops) < 1 {
2018-06-07 19:24:02 +00:00
p.core.peers.removePeer(p.port)
}
var loc switchLocator
prevKey := msg.Root
for idx, hop := range msg.Hops {
// Check signatures and collect coords for dht
sigMsg := msg
sigMsg.Hops = msg.Hops[:idx]
loc.coords = append(loc.coords, hop.Port)
bs := getBytesForSig(&hop.Next, &sigMsg)
if !crypto.Verify(&prevKey, bs, &hop.Sig) {
2018-06-07 19:24:02 +00:00
p.core.peers.removePeer(p.port)
}
prevKey = hop.Next
}
p.core.switchTable.handleMsg(&msg, p.port)
if !p.core.switchTable.checkRoot(&msg) {
// Bad switch message
2018-12-15 03:44:31 +00:00
p.dinfo <- nil
return
}
// Pass a mesage to the dht informing it that this peer (still) exists
loc.coords = loc.coords[:len(loc.coords)-1]
2018-01-04 22:37:51 +00:00
dinfo := dhtInfo{
key: p.box,
coords: loc.getCoords(),
2018-01-04 22:37:51 +00:00
}
2018-12-15 03:44:31 +00:00
p.dinfo <- &dinfo
2017-12-29 04:16:20 +00:00
}
// This generates the bytes that we sign or check the signature of for a switchMsg.
2019-01-09 09:42:07 +00:00
// It begins with the next node's key, followed by the root and the timestamp, followed by coords being advertised to the next node.
func getBytesForSig(next *crypto.SigPubKey, msg *switchMsg) []byte {
var loc switchLocator
for _, hop := range msg.Hops {
loc.coords = append(loc.coords, hop.Port)
}
2018-01-04 22:37:51 +00:00
bs := append([]byte(nil), next[:]...)
bs = append(bs, msg.Root[:]...)
bs = append(bs, wire_encode_uint64(wire_intToUint(msg.TStamp))...)
bs = append(bs, wire_encode_coords(loc.getCoords())...)
2018-01-04 22:37:51 +00:00
return bs
2017-12-29 04:16:20 +00:00
}