mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2024-11-27 12:05:23 +00:00
437 lines
12 KiB
Go
437 lines
12 KiB
Go
package yggdrasil
|
|
|
|
// TODO cleanup, this file is kind of a mess
|
|
// Commented code should be removed
|
|
// Live code should be better commented
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"time"
|
|
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
|
|
|
"github.com/Arceliar/phony"
|
|
)
|
|
|
|
// The peers struct represents peers with an active connection.
|
|
// 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, its link protocol traffic is used to build the spanning tree, in which case this checks signatures and passes the message along to the switch.
|
|
type peers struct {
|
|
phony.Inbox
|
|
core *Core
|
|
ports map[switchPort]*peer // use CoW semantics, share updated version with each peer
|
|
table *lookupTable // Sent from switch, share updated version with each peer
|
|
}
|
|
|
|
// Initializes the peers struct.
|
|
func (ps *peers) init(c *Core) {
|
|
ps.core = c
|
|
ps.ports = make(map[switchPort]*peer)
|
|
ps.table = new(lookupTable)
|
|
}
|
|
|
|
func (ps *peers) reconfigure() {
|
|
// This is where reconfiguration would go, if we had anything to do
|
|
}
|
|
|
|
// 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.config.Mutex.RLock()
|
|
defer ps.core.config.Mutex.RUnlock()
|
|
for _, v := range ps.core.config.Current.AllowedEncryptionPublicKeys {
|
|
if v == boxstr {
|
|
return true
|
|
}
|
|
}
|
|
return len(ps.core.config.Current.AllowedEncryptionPublicKeys) == 0
|
|
}
|
|
|
|
// Adds a key to the whitelist.
|
|
func (ps *peers) addAllowedEncryptionPublicKey(box string) {
|
|
ps.core.config.Mutex.RLock()
|
|
defer ps.core.config.Mutex.RUnlock()
|
|
ps.core.config.Current.AllowedEncryptionPublicKeys =
|
|
append(ps.core.config.Current.AllowedEncryptionPublicKeys, box)
|
|
}
|
|
|
|
// Removes a key from the whitelist.
|
|
func (ps *peers) removeAllowedEncryptionPublicKey(box string) {
|
|
ps.core.config.Mutex.RLock()
|
|
defer ps.core.config.Mutex.RUnlock()
|
|
for k, v := range ps.core.config.Current.AllowedEncryptionPublicKeys {
|
|
if v == box {
|
|
ps.core.config.Current.AllowedEncryptionPublicKeys =
|
|
append(ps.core.config.Current.AllowedEncryptionPublicKeys[:k],
|
|
ps.core.config.Current.AllowedEncryptionPublicKeys[k+1:]...)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gets the whitelist of allowed keys for incoming connections.
|
|
func (ps *peers) getAllowedEncryptionPublicKeys() []string {
|
|
ps.core.config.Mutex.RLock()
|
|
defer ps.core.config.Mutex.RUnlock()
|
|
return ps.core.config.Current.AllowedEncryptionPublicKeys
|
|
}
|
|
|
|
// Information known about a peer, including their box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic
|
|
type peer struct {
|
|
phony.Inbox
|
|
core *Core
|
|
intf linkInterface
|
|
port switchPort
|
|
box crypto.BoxPubKey
|
|
sig crypto.SigPubKey
|
|
shared crypto.BoxSharedKey
|
|
linkShared crypto.BoxSharedKey
|
|
endpoint string
|
|
firstSeen time.Time // To track uptime for getPeers
|
|
dinfo *dhtInfo // used to keep the DHT working
|
|
// The below aren't actually useful internally, they're just gathered for getPeers statistics
|
|
bytesSent uint64
|
|
bytesRecvd uint64
|
|
ports map[switchPort]*peer
|
|
table *lookupTable
|
|
queue packetQueue
|
|
max uint64
|
|
seq uint64 // this and idle are used to detect when to drop packets from queue
|
|
idle bool
|
|
drop bool // set to true if we're dropping packets from the queue
|
|
}
|
|
|
|
func (ps *peers) updateTables(from phony.Actor, table *lookupTable) {
|
|
ps.Act(from, func() {
|
|
ps.table = table
|
|
ps._updatePeers()
|
|
})
|
|
}
|
|
|
|
func (ps *peers) _updatePeers() {
|
|
ports := ps.ports
|
|
table := ps.table
|
|
for _, peer := range ps.ports {
|
|
p := peer // peer is mutated during iteration
|
|
p.Act(ps, func() {
|
|
p.ports = ports
|
|
p.table = table
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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, intf linkInterface) *peer {
|
|
now := time.Now()
|
|
p := peer{box: *box,
|
|
core: ps.core,
|
|
intf: intf,
|
|
sig: *sig,
|
|
shared: *crypto.GetSharedKey(&ps.core.boxPriv, box),
|
|
linkShared: *linkShared,
|
|
firstSeen: now,
|
|
}
|
|
oldPorts := ps.ports
|
|
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.ports = newPorts
|
|
ps._updatePeers()
|
|
return &p
|
|
}
|
|
|
|
func (p *peer) _removeSelf() {
|
|
p.core.peers.Act(p, func() {
|
|
p.core.peers._removePeer(p)
|
|
})
|
|
}
|
|
|
|
// Removes a peer for a given port, if one exists.
|
|
func (ps *peers) _removePeer(p *peer) {
|
|
if q := ps.ports[p.port]; p.port == 0 || q != p {
|
|
return
|
|
} // Can't remove self peer or nonexistant peer
|
|
ps.core.switchTable.forgetPeer(ps, p.port)
|
|
oldPorts := ps.ports
|
|
newPorts := make(map[switchPort]*peer)
|
|
for k, v := range oldPorts {
|
|
newPorts[k] = v
|
|
}
|
|
delete(newPorts, p.port)
|
|
p.intf.close()
|
|
ps.ports = newPorts
|
|
ps._updatePeers()
|
|
}
|
|
|
|
// 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(from phony.Actor) {
|
|
ps.Act(from, func() {
|
|
for _, peer := range ps.ports {
|
|
p := peer
|
|
if p.port == 0 {
|
|
continue
|
|
}
|
|
p.Act(ps, p._sendSwitchMsg)
|
|
}
|
|
})
|
|
}
|
|
|
|
func (ps *peers) updateDHT(from phony.Actor) {
|
|
ps.Act(from, func() {
|
|
for _, peer := range ps.ports {
|
|
p := peer
|
|
if p.port == 0 {
|
|
continue
|
|
}
|
|
p.Act(ps, p._updateDHT)
|
|
}
|
|
})
|
|
}
|
|
|
|
// This must be launched in a separate goroutine by whatever sets up the peer struct.
|
|
func (p *peer) start() {
|
|
// Just for good measure, immediately send a switch message to this peer when we start
|
|
p.Act(nil, p._sendSwitchMsg)
|
|
}
|
|
|
|
func (p *peer) _updateDHT() {
|
|
if p.dinfo != nil {
|
|
p.core.router.insertPeer(p, p.dinfo)
|
|
}
|
|
}
|
|
|
|
func (p *peer) handlePacketFrom(from phony.Actor, packet []byte) {
|
|
p.Act(from, func() {
|
|
p._handlePacket(packet)
|
|
})
|
|
}
|
|
|
|
// Called to handle incoming packets.
|
|
// Passes the packet to a handler for that packet type.
|
|
func (p *peer) _handlePacket(packet []byte) {
|
|
// FIXME this is off by stream padding and msg length overhead, should be done in tcp.go
|
|
p.bytesRecvd += uint64(len(packet))
|
|
pType, pTypeLen := wire_decode_uint64(packet)
|
|
if pTypeLen == 0 {
|
|
return
|
|
}
|
|
switch pType {
|
|
case wire_Traffic:
|
|
p._handleTraffic(packet)
|
|
case wire_ProtocolTraffic:
|
|
p._handleTraffic(packet)
|
|
case wire_LinkProtocolTraffic:
|
|
p._handleLinkTraffic(packet)
|
|
default:
|
|
}
|
|
}
|
|
|
|
// Get the coords of a packet without decoding
|
|
func peer_getPacketCoords(packet []byte) []byte {
|
|
_, pTypeLen := wire_decode_uint64(packet)
|
|
coords, _ := wire_decode_coords(packet[pTypeLen:])
|
|
return coords
|
|
}
|
|
|
|
// 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.
|
|
func (p *peer) _handleTraffic(packet []byte) {
|
|
if _, isIn := p.table.elems[p.port]; !isIn && p.port != 0 {
|
|
// Drop traffic if the peer isn't in the switch
|
|
return
|
|
}
|
|
coords := peer_getPacketCoords(packet)
|
|
next := p.table.lookup(coords)
|
|
if nPeer, isIn := p.ports[next]; isIn {
|
|
nPeer.sendPacketFrom(p, packet)
|
|
}
|
|
//p.core.switchTable.packetInFrom(p, packet)
|
|
}
|
|
|
|
func (p *peer) sendPacketFrom(from phony.Actor, packet []byte) {
|
|
p.Act(from, func() {
|
|
p._sendPacket(packet)
|
|
})
|
|
}
|
|
|
|
func (p *peer) _sendPacket(packet []byte) {
|
|
p.queue.push(packet)
|
|
if p.idle {
|
|
p.idle = false
|
|
p._handleIdle()
|
|
} else if p.drop {
|
|
for p.queue.size > p.max {
|
|
p.queue.drop()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *peer) _handleIdle() {
|
|
var packets [][]byte
|
|
var size uint64
|
|
for {
|
|
if packet, success := p.queue.pop(); success {
|
|
packets = append(packets, packet)
|
|
size += uint64(len(packet))
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
p.seq++
|
|
if len(packets) > 0 {
|
|
p.bytesSent += uint64(size)
|
|
p.intf.out(packets)
|
|
p.max = p.queue.size
|
|
} else {
|
|
p.idle = true
|
|
}
|
|
p.drop = false
|
|
}
|
|
|
|
func (p *peer) notifyBlocked(from phony.Actor) {
|
|
p.Act(from, func() {
|
|
seq := p.seq
|
|
p.Act(nil, func() {
|
|
if seq == p.seq {
|
|
p.drop = true
|
|
p.max = 2*p.queue.size + streamMsgSize
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
// 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.
|
|
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)
|
|
linkPacket := wire_linkProtoTrafficPacket{
|
|
Nonce: *nonce,
|
|
Payload: bs,
|
|
}
|
|
packet = linkPacket.encode()
|
|
p.intf.linkOut(packet)
|
|
}
|
|
|
|
// Decrypts the outer (permanent) and inner (ephemeral) crypto layers on link traffic.
|
|
// Identifies the link traffic type and calls the appropriate handler.
|
|
func (p *peer) _handleLinkTraffic(bs []byte) {
|
|
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)
|
|
if !isOK {
|
|
return
|
|
}
|
|
pType, pTypeLen := wire_decode_uint64(payload)
|
|
if pTypeLen == 0 {
|
|
return
|
|
}
|
|
switch pType {
|
|
case wire_SwitchMsg:
|
|
p._handleSwitchMsg(payload)
|
|
default:
|
|
}
|
|
}
|
|
|
|
// 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.table.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 {
|
|
p._removeSelf()
|
|
return
|
|
}
|
|
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) {
|
|
p._removeSelf()
|
|
return
|
|
}
|
|
prevKey = hop.Next
|
|
}
|
|
p.core.switchTable.Act(p, func() {
|
|
if !p.core.switchTable._checkRoot(&msg) {
|
|
// Bad switch message
|
|
p.Act(&p.core.switchTable, func() {
|
|
p.dinfo = nil
|
|
})
|
|
} else {
|
|
// handle the message
|
|
p.core.switchTable._handleMsg(&msg, p.port, false)
|
|
p.Act(&p.core.switchTable, func() {
|
|
// Pass a message to the dht informing it that this peer (still) exists
|
|
loc.coords = loc.coords[:len(loc.coords)-1]
|
|
p.dinfo = &dhtInfo{
|
|
key: p.box,
|
|
coords: loc.getCoords(),
|
|
}
|
|
p._updateDHT()
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// This generates the bytes that we sign or check the signature of for a switchMsg.
|
|
// 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)
|
|
}
|
|
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())...)
|
|
return bs
|
|
}
|