5
0
mirror of https://github.com/cwinfo/yggdrasil-go.git synced 2024-11-25 21:51:38 +00:00

Merge pull request #318 from yggdrasil-network/link

Implement link interface
This commit is contained in:
Arceliar 2019-02-03 16:21:45 -06:00 committed by GitHub
commit f747f259b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 595 additions and 427 deletions

View File

@ -35,22 +35,22 @@ func main() {
} }
var encryptionKeys []keySet var encryptionKeys []keySet
for i := 0; i < *numHosts + 1; i++ { for i := 0; i < *numHosts+1; i++ {
encryptionKeys = append(encryptionKeys, newBoxKey()) encryptionKeys = append(encryptionKeys, newBoxKey())
} }
encryptionKeys = sortKeySetArray(encryptionKeys) encryptionKeys = sortKeySetArray(encryptionKeys)
for i := 0; i < *keyTries - *numHosts - 1; i++ { for i := 0; i < *keyTries-*numHosts-1; i++ {
encryptionKeys[0] = newBoxKey(); encryptionKeys[0] = newBoxKey()
encryptionKeys = bubbleUpTo(encryptionKeys, 0) encryptionKeys = bubbleUpTo(encryptionKeys, 0)
} }
var signatureKeys []keySet var signatureKeys []keySet
for i := 0; i < *numHosts + 1; i++ { for i := 0; i < *numHosts+1; i++ {
signatureKeys = append(signatureKeys, newSigKey()) signatureKeys = append(signatureKeys, newSigKey())
} }
signatureKeys = sortKeySetArray(signatureKeys) signatureKeys = sortKeySetArray(signatureKeys)
for i := 0; i < *keyTries - *numHosts - 1; i++ { for i := 0; i < *keyTries-*numHosts-1; i++ {
signatureKeys[0] = newSigKey(); signatureKeys[0] = newSigKey()
signatureKeys = bubbleUpTo(signatureKeys, 0) signatureKeys = bubbleUpTo(signatureKeys, 0)
} }
@ -112,11 +112,11 @@ func sortKeySetArray(sets []keySet) []keySet {
} }
func bubbleUpTo(sets []keySet, num int) []keySet { func bubbleUpTo(sets []keySet, num int) []keySet {
for i := 0; i < len(sets) - num - 1; i++ { for i := 0; i < len(sets)-num-1; i++ {
if isBetter(sets[i + 1].id, sets[i].id) { if isBetter(sets[i+1].id, sets[i].id) {
var tmp = sets[i] var tmp = sets[i]
sets[i] = sets[i + 1] sets[i] = sets[i+1]
sets[i + 1] = tmp sets[i+1] = tmp
} else { } else {
break break
} }

2
go.mod
View File

@ -2,10 +2,10 @@ module github.com/yggdrasil-network/yggdrasil-go
require ( require (
github.com/docker/libcontainer v2.2.1+incompatible github.com/docker/libcontainer v2.2.1+incompatible
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0
github.com/mitchellh/mapstructure v1.1.2 github.com/mitchellh/mapstructure v1.1.2
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9

4
go.sum
View File

@ -1,5 +1,7 @@
github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0= github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0=
github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw= github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 h1:xmvkbxXDeN1ffWq8kvrhyqVYAO2aXuRBsbpxVTR+JyU= github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 h1:xmvkbxXDeN1ffWq8kvrhyqVYAO2aXuRBsbpxVTR+JyU=
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g=
@ -18,5 +20,3 @@ golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dz
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=

View File

@ -3,6 +3,7 @@ package util
// These are misc. utility functions that didn't really fit anywhere else // These are misc. utility functions that didn't really fit anywhere else
import "runtime" import "runtime"
import "time"
// A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere. // A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere.
func Yield() { func Yield() {
@ -44,3 +45,14 @@ func PutBytes(bs []byte) {
default: default:
} }
} }
// This is a workaround to go's broken timer implementation
func TimerStop(t *time.Timer) bool {
if !t.Stop() {
select {
case <-t.C:
default:
}
}
return true
}

View File

@ -2,147 +2,97 @@ package yggdrasil
import ( import (
"errors" "errors"
"fmt" "io"
"sync" "sync"
"sync/atomic"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
) )
type awdl struct { type awdl struct {
core *Core link *link
mutex sync.RWMutex // protects interfaces below mutex sync.RWMutex // protects interfaces below
interfaces map[string]*awdlInterface interfaces map[string]*awdlInterface
} }
type awdlInterface struct { type awdlInterface struct {
awdl *awdl linkif *linkInterface
fromAWDL chan []byte rwc awdlReadWriteCloser
toAWDL chan []byte peer *peer
shutdown chan bool stream stream
peer *peer
} }
func (l *awdl) init(c *Core) error { type awdlReadWriteCloser struct {
l.core = c fromAWDL chan []byte
l.mutex.Lock() toAWDL chan []byte
l.interfaces = make(map[string]*awdlInterface) }
l.mutex.Unlock()
func (c awdlReadWriteCloser) Read(p []byte) (n int, err error) {
if packet, ok := <-c.fromAWDL; ok {
n = copy(p, packet)
return n, nil
}
return 0, io.EOF
}
func (c awdlReadWriteCloser) Write(p []byte) (n int, err error) {
var pc []byte
pc = append(pc, p...)
c.toAWDL <- pc
return len(pc), nil
}
func (c awdlReadWriteCloser) Close() error {
close(c.fromAWDL)
close(c.toAWDL)
return nil
}
func (a *awdl) init(l *link) error {
a.link = l
a.mutex.Lock()
a.interfaces = make(map[string]*awdlInterface)
a.mutex.Unlock()
return nil return nil
} }
func (l *awdl) create(fromAWDL chan []byte, toAWDL chan []byte /*boxPubKey *crypto.BoxPubKey, sigPubKey *crypto.SigPubKey*/, name string) (*awdlInterface, error) { func (a *awdl) create(name, local, remote string, incoming bool) (*awdlInterface, error) {
rwc := awdlReadWriteCloser{
fromAWDL: make(chan []byte, 1),
toAWDL: make(chan []byte, 1),
}
s := stream{}
s.init(rwc)
linkif, err := a.link.create(&s, name, "awdl", local, remote, incoming, true)
if err != nil {
return nil, err
}
intf := awdlInterface{ intf := awdlInterface{
awdl: l, linkif: linkif,
fromAWDL: fromAWDL, rwc: rwc,
toAWDL: toAWDL,
shutdown: make(chan bool),
} }
l.mutex.Lock() a.mutex.Lock()
l.interfaces[name] = &intf a.interfaces[name] = &intf
l.mutex.Unlock() a.mutex.Unlock()
myLinkPub, myLinkPriv := crypto.NewBoxKeys() go intf.linkif.handler()
meta := version_getBaseMetadata() return &intf, nil
meta.box = l.core.boxPub
meta.sig = l.core.sigPub
meta.link = *myLinkPub
metaBytes := meta.encode()
l.core.log.Traceln("toAWDL <- metaBytes")
toAWDL <- metaBytes
l.core.log.Traceln("metaBytes = <-fromAWDL")
metaBytes = <-fromAWDL
l.core.log.Traceln("version_metadata{}")
meta = version_metadata{}
if !meta.decode(metaBytes) || !meta.check() {
return nil, errors.New("Metadata decode failure")
}
l.core.log.Traceln("version_getBaseMetadata{}")
base := version_getBaseMetadata()
if meta.ver > base.ver || meta.ver == base.ver && meta.minorVer > base.minorVer {
return nil, errors.New("Failed to connect to node: " + name + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer))
}
l.core.log.Traceln("crypto.GetSharedKey")
shared := crypto.GetSharedKey(myLinkPriv, &meta.link)
//shared := crypto.GetSharedKey(&l.core.boxPriv, boxPubKey)
l.core.log.Traceln("l.core.peers.newPeer")
intf.peer = l.core.peers.newPeer(&meta.box, &meta.sig, shared, name)
if intf.peer != nil {
intf.peer.linkOut = make(chan []byte, 1) // protocol traffic
intf.peer.out = func(msg []byte) {
defer func() { recover() }()
intf.toAWDL <- msg
} // called by peer.sendPacket()
l.core.switchTable.idleIn <- intf.peer.port // notify switch that we're idle
intf.peer.close = func() {
close(intf.fromAWDL)
close(intf.toAWDL)
}
go intf.handler()
go intf.peer.linkLoop()
return &intf, nil
}
delete(l.interfaces, name)
return nil, errors.New("l.core.peers.newPeer failed")
} }
func (l *awdl) getInterface(identity string) *awdlInterface { func (a *awdl) getInterface(identity string) *awdlInterface {
l.mutex.RLock() a.mutex.RLock()
defer l.mutex.RUnlock() defer a.mutex.RUnlock()
if intf, ok := l.interfaces[identity]; ok { if intf, ok := a.interfaces[identity]; ok {
return intf return intf
} }
return nil return nil
} }
func (l *awdl) shutdown(identity string) error { func (a *awdl) shutdown(identity string) error {
if intf, ok := l.interfaces[identity]; ok { if intf, ok := a.interfaces[identity]; ok {
intf.shutdown <- true close(intf.linkif.closed)
l.core.peers.removePeer(intf.peer.port) intf.rwc.Close()
l.mutex.Lock() a.mutex.Lock()
delete(l.interfaces, identity) delete(a.interfaces, identity)
l.mutex.Unlock() a.mutex.Unlock()
return nil return nil
} else {
return errors.New(fmt.Sprintf("Interface '%s' doesn't exist or already shutdown", identity))
}
}
func (ai *awdlInterface) handler() {
send := func(msg []byte) {
ai.toAWDL <- msg
atomic.AddUint64(&ai.peer.bytesSent, uint64(len(msg)))
util.PutBytes(msg)
}
for {
timerInterval := tcp_ping_interval
timer := time.NewTimer(timerInterval)
defer timer.Stop()
select {
case p := <-ai.peer.linkOut:
send(p)
continue
default:
}
timer.Stop()
select {
case <-timer.C:
default:
}
timer.Reset(timerInterval)
select {
case _ = <-timer.C:
send([]byte{})
case p := <-ai.peer.linkOut:
send(p)
continue
case r := <-ai.fromAWDL:
ai.peer.handlePacket(r)
ai.awdl.core.switchTable.idleIn <- ai.peer.port
case <-ai.shutdown:
return
}
} }
return errors.New("Interface not found or already closed")
} }

View File

@ -45,7 +45,7 @@ type Core struct {
searches searches searches searches
multicast multicast multicast multicast
tcp tcpInterface tcp tcpInterface
awdl awdl link link
log *log.Logger log *log.Logger
} }
@ -199,8 +199,8 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
return err return err
} }
if err := c.awdl.init(c); err != nil { if err := c.link.init(c); err != nil {
c.log.Errorln("Failed to start AWDL interface") c.log.Errorln("Failed to start link interfaces")
return err return err
} }

View File

@ -97,9 +97,7 @@ func (c *Core) DEBUG_getPeers() *peers {
} }
func (ps *peers) DEBUG_newPeer(box crypto.BoxPubKey, sig crypto.SigPubKey, link crypto.BoxSharedKey) *peer { func (ps *peers) DEBUG_newPeer(box crypto.BoxPubKey, sig crypto.SigPubKey, link crypto.BoxSharedKey) *peer {
//in <-chan []byte, return ps.newPeer(&box, &sig, &link, "(simulator)", nil)
//out chan<- []byte) *peer {
return ps.newPeer(&box, &sig, &link, "(simulator)") //, in, out)
} }
/* /*

311
src/yggdrasil/link.go Normal file
View File

@ -0,0 +1,311 @@
package yggdrasil
import (
"encoding/hex"
"errors"
"fmt"
"net"
"strings"
"sync"
//"sync/atomic"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
type link struct {
core *Core
mutex sync.RWMutex // protects interfaces below
interfaces map[linkInfo]*linkInterface
awdl awdl // AWDL interface support
// TODO timeout (to remove from switch), read from config.ReadTimeout
}
type linkInfo struct {
box crypto.BoxPubKey // Their encryption key
sig crypto.SigPubKey // Their signing key
linkType string // Type of link, e.g. TCP, AWDL
local string // Local name or address
remote string // Remote name or address
}
type linkInterfaceMsgIO interface {
readMsg() ([]byte, error)
writeMsg([]byte) (int, error)
close() error
// These are temporary workarounds to stream semantics
_sendMetaBytes([]byte) error
_recvMetaBytes() ([]byte, error)
}
type linkInterface struct {
name string
link *link
peer *peer
msgIO linkInterfaceMsgIO
info linkInfo
incoming bool
force bool
closed chan struct{}
}
func (l *link) init(c *Core) error {
l.core = c
l.mutex.Lock()
l.interfaces = make(map[linkInfo]*linkInterface)
l.mutex.Unlock()
if err := l.awdl.init(l); err != nil {
l.core.log.Errorln("Failed to start AWDL interface")
return err
}
return nil
}
func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) {
// Technically anything unique would work for names, but lets pick something human readable, just for debugging
intf := linkInterface{
name: name,
link: l,
msgIO: msgIO,
info: linkInfo{
linkType: linkType,
local: local,
remote: remote,
},
incoming: incoming,
force: force,
}
return &intf, nil
}
func (intf *linkInterface) handler() error {
// TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later
myLinkPub, myLinkPriv := crypto.NewBoxKeys()
meta := version_getBaseMetadata()
meta.box = intf.link.core.boxPub
meta.sig = intf.link.core.sigPub
meta.link = *myLinkPub
metaBytes := meta.encode()
// TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer)
err := intf.msgIO._sendMetaBytes(metaBytes)
if err != nil {
return err
}
metaBytes, err = intf.msgIO._recvMetaBytes()
if err != nil {
return err
}
meta = version_metadata{}
if !meta.decode(metaBytes) || !meta.check() {
return errors.New("failed to decode metadata")
}
base := version_getBaseMetadata()
if meta.ver > base.ver || meta.ver == base.ver && meta.minorVer > base.minorVer {
intf.link.core.log.Errorln("Failed to connect to node: " + intf.name + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer))
return errors.New("failed to connect: wrong version")
}
// Check if we're authorized to connect to this key / IP
if !intf.force && !intf.link.core.peers.isAllowedEncryptionPublicKey(&meta.box) {
intf.link.core.log.Debugf("%s connection to %s forbidden: AllowedEncryptionPublicKeys does not contain key %s",
strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.box[:]))
intf.msgIO.close()
return nil
}
// Check if we already have a link to this node
intf.info.box = meta.box
intf.info.sig = meta.sig
intf.link.mutex.Lock()
if oldIntf, isIn := intf.link.interfaces[intf.info]; isIn {
intf.link.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.
intf.link.core.log.Debugln("DEBUG: found existing interface for", intf.name)
intf.msgIO.close()
<-oldIntf.closed
return nil
} else {
intf.closed = make(chan struct{})
intf.link.interfaces[intf.info] = intf
defer func() {
intf.link.mutex.Lock()
delete(intf.link.interfaces, intf.info)
intf.link.mutex.Unlock()
close(intf.closed)
}()
intf.link.core.log.Debugln("DEBUG: registered interface for", intf.name)
}
intf.link.mutex.Unlock()
// Create peer
shared := crypto.GetSharedKey(myLinkPriv, &meta.link)
intf.peer = intf.link.core.peers.newPeer(&meta.box, &meta.sig, shared, intf.name, func() { intf.msgIO.close() })
if intf.peer == nil {
return errors.New("failed to create peer")
}
defer func() {
// More cleanup can go here
intf.link.core.peers.removePeer(intf.peer.port)
}()
// Finish setting up the peer struct
out := make(chan []byte, 1)
defer close(out)
intf.peer.out = func(msg []byte) {
defer func() { recover() }()
out <- msg
}
intf.peer.linkOut = make(chan []byte, 1)
themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box))
themAddrString := net.IP(themAddr[:]).String()
themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote)
intf.link.core.log.Infof("Connected %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
defer intf.link.core.log.Infof("Disconnected %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
// Start the link loop
go intf.peer.linkLoop()
// Start the writer
signalReady := make(chan struct{}, 1)
signalSent := make(chan bool, 1)
sendAck := make(chan struct{}, 1)
go func() {
defer close(signalReady)
defer close(signalSent)
interval := 4 * time.Second
tcpTimer := time.NewTimer(interval) // used for backwards compat with old tcp
defer util.TimerStop(tcpTimer)
send := func(bs []byte) {
intf.msgIO.writeMsg(bs)
select {
case signalSent <- len(bs) > 0:
default:
}
}
for {
// First try to send any link protocol traffic
select {
case msg := <-intf.peer.linkOut:
send(msg)
continue
default:
}
// No protocol traffic to send, so reset the timer
util.TimerStop(tcpTimer)
tcpTimer.Reset(interval)
// Now block until something is ready or the timer triggers keepalive traffic
select {
case <-tcpTimer.C:
intf.link.core.log.Debugf("Sending (legacy) keep-alive to %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
send(nil)
case <-sendAck:
intf.link.core.log.Debugf("Sending ack to %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
send(nil)
case msg := <-intf.peer.linkOut:
intf.msgIO.writeMsg(msg)
case msg, ok := <-out:
if !ok {
return
}
send(msg)
util.PutBytes(msg)
select {
case signalReady <- struct{}{}:
default:
}
}
}
}()
//intf.link.core.switchTable.idleIn <- intf.peer.port // notify switch that we're idle
// Used to enable/disable activity in the switch
signalAlive := make(chan bool, 1) // True = real packet, false = keep-alive
defer close(signalAlive)
go func() {
var isAlive bool
var isReady bool
var sendTimerRunning bool
var recvTimerRunning bool
recvTime := 6 * time.Second // TODO set to ReadTimeout from the config, reset if it gets changed
sendTime := time.Second
sendTimer := time.NewTimer(sendTime)
defer util.TimerStop(sendTimer)
recvTimer := time.NewTimer(recvTime)
defer util.TimerStop(recvTimer)
for {
select {
case gotMsg, ok := <-signalAlive:
if !ok {
return
}
if !isAlive {
isAlive = true
if !isReady {
// (Re-)enable in the switch
isReady = true
intf.link.core.switchTable.idleIn <- intf.peer.port
}
}
if gotMsg && !sendTimerRunning {
// We got a message
// Start a timer, if it expires then send a 0-sized ack to let them know we're alive
util.TimerStop(sendTimer)
sendTimer.Reset(sendTime)
sendTimerRunning = true
}
case sentMsg, ok := <-signalSent:
// Stop any running ack timer
if !ok {
return
}
util.TimerStop(sendTimer)
sendTimerRunning = false
if sentMsg && !recvTimerRunning {
// We sent a message
// Start a timer, if it expires and we haven't gotten any return traffic (including a 0-sized ack), then assume there's a problem
util.TimerStop(recvTimer)
recvTimer.Reset(recvTime)
recvTimerRunning = true
}
case _, ok := <-signalReady:
if !ok {
return
}
if !isAlive || !isReady {
// Disable in the switch
isReady = false
} else {
// Keep enabled in the switch
intf.link.core.switchTable.idleIn <- intf.peer.port
}
case <-sendTimer.C:
// We haven't sent anything, so signal a send of a 0 packet to let them know we're alive
select {
case sendAck <- struct{}{}:
default:
}
case <-recvTimer.C:
// We haven't received anything, so assume there's a problem and don't return this node to the switch until they start responding
isAlive = false
}
}
}()
// Run reader loop
for {
msg, err := intf.msgIO.readMsg()
if len(msg) > 0 {
intf.peer.handlePacket(msg)
}
if err != nil {
return err
}
select {
case signalAlive <- len(msg) > 0:
default:
}
}
////////////////////////////////////////////////////////////////////////////////
return nil
}

View File

@ -5,11 +5,11 @@ package yggdrasil
import ( import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"log"
"os" "os"
"regexp"
"time" "time"
"github.com/gologme/log"
hjson "github.com/hjson/hjson-go" hjson "github.com/hjson/hjson-go"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/config"
@ -52,10 +52,6 @@ func (c *Core) StartAutoconfigure() error {
if hostname, err := os.Hostname(); err == nil { if hostname, err := os.Hostname(); err == nil {
nc.NodeInfo = map[string]interface{}{"name": hostname} nc.NodeInfo = map[string]interface{}{"name": hostname}
} }
ifceExpr, err := regexp.Compile(".*")
if err == nil {
c.ifceExpr = append(c.ifceExpr, ifceExpr)
}
if err := c.Start(nc, logger); err != nil { if err := c.Start(nc, logger); err != nil {
return err return err
} }
@ -77,13 +73,6 @@ func (c *Core) StartJSON(configjson []byte) error {
return err return err
} }
nc.IfName = "dummy" nc.IfName = "dummy"
for _, ll := range nc.MulticastInterfaces {
ifceExpr, err := regexp.Compile(ll)
if err != nil {
panic(err)
}
c.AddMulticastInterfaceExpr(ifceExpr)
}
if err := c.Start(nc, logger); err != nil { if err := c.Start(nc, logger); err != nil {
return err return err
} }

View File

@ -29,39 +29,33 @@ func (nsl MobileLogger) Write(p []byte) (n int, err error) {
return len(p), nil return len(p), nil
} }
func (c *Core) AWDLCreateInterface(name string) error { func (c *Core) AWDLCreateInterface(name, local, remote string, incoming bool) error {
fromAWDL := make(chan []byte, 32) if intf, err := c.link.awdl.create(name, local, remote, incoming); err != nil || intf == nil {
toAWDL := make(chan []byte, 32) c.log.Println("c.link.awdl.create:", err)
if intf, err := c.awdl.create(fromAWDL, toAWDL, name); err == nil {
if intf != nil {
c.log.Println(err)
return err
} else {
c.log.Println("c.awdl.create didn't return an interface")
return errors.New("c.awdl.create didn't return an interface")
}
} else {
c.log.Println(err)
return err return err
} }
return nil
} }
func (c *Core) AWDLShutdownInterface(name string) error { func (c *Core) AWDLShutdownInterface(name string) error {
return c.awdl.shutdown(name) return c.link.awdl.shutdown(name)
} }
func (c *Core) AWDLRecvPacket(identity string) ([]byte, error) { func (c *Core) AWDLRecvPacket(identity string) ([]byte, error) {
if intf := c.awdl.getInterface(identity); intf != nil { if intf := c.link.awdl.getInterface(identity); intf != nil {
return <-intf.toAWDL, nil read, ok := <-intf.rwc.toAWDL
if !ok {
return nil, errors.New("AWDLRecvPacket: channel closed")
}
return read, nil
} }
return nil, errors.New("AWDLRecvPacket identity not known: " + identity) return nil, errors.New("AWDLRecvPacket identity not known: " + identity)
} }
func (c *Core) AWDLSendPacket(identity string, buf []byte) error { func (c *Core) AWDLSendPacket(identity string, buf []byte) error {
packet := append(util.GetBytes(), buf[:]...) packet := append(util.GetBytes(), buf[:]...)
if intf := c.awdl.getInterface(identity); intf != nil { if intf := c.link.awdl.getInterface(identity); intf != nil {
intf.fromAWDL <- packet intf.rwc.fromAWDL <- packet
return nil return nil
} }
return errors.New("AWDLSendPacket identity not known: " + identity) return errors.New("AWDLSendPacket identity not known: " + identity)

View File

@ -113,7 +113,7 @@ type peer struct {
} }
// Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unoccupied port number. // 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) *peer { func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, endpoint string, closer func()) *peer {
now := time.Now() now := time.Now()
p := peer{box: *box, p := peer{box: *box,
sig: *sig, sig: *sig,
@ -123,6 +123,7 @@ func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShare
firstSeen: now, firstSeen: now,
doSend: make(chan struct{}, 1), doSend: make(chan struct{}, 1),
dinfo: make(chan *dhtInfo, 1), dinfo: make(chan *dhtInfo, 1),
close: closer,
core: ps.core} core: ps.core}
ps.mutex.Lock() ps.mutex.Lock()
defer ps.mutex.Unlock() defer ps.mutex.Unlock()
@ -250,6 +251,7 @@ func (p *peer) handleTraffic(packet []byte, pTypeLen int) {
func (p *peer) sendPacket(packet []byte) { func (p *peer) sendPacket(packet []byte) {
// Is there ever a case where something more complicated is needed? // Is there ever a case where something more complicated is needed?
// What if p.out blocks? // What if p.out blocks?
atomic.AddUint64(&p.bytesSent, uint64(len(packet)))
p.out(packet) p.out(packet)
} }

View File

@ -67,7 +67,7 @@ func (r *router) init(core *Core) {
r.addr = *address.AddrForNodeID(&r.core.dht.nodeID) r.addr = *address.AddrForNodeID(&r.core.dht.nodeID)
r.subnet = *address.SubnetForNodeID(&r.core.dht.nodeID) r.subnet = *address.SubnetForNodeID(&r.core.dht.nodeID)
in := make(chan []byte, 32) // TODO something better than this... in := make(chan []byte, 32) // TODO something better than this...
p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, "(self)") p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, "(self)", nil)
p.out = func(packet []byte) { p.out = func(packet []byte) {
// This is to make very sure it never blocks // This is to make very sure it never blocks
select { select {

141
src/yggdrasil/stream.go Normal file
View File

@ -0,0 +1,141 @@
package yggdrasil
import (
"errors"
"fmt"
"io"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// Test that this matches the interface we expect
var _ = linkInterfaceMsgIO(&stream{})
type stream struct {
rwc io.ReadWriteCloser
inputBuffer []byte // Incoming packet stream
}
func (s *stream) close() error {
return s.rwc.Close()
}
const streamMsgSize = 2048 + 65535
var streamMsg = [...]byte{0xde, 0xad, 0xb1, 0x75} // "dead bits"
func (s *stream) init(rwc io.ReadWriteCloser) {
// TODO have this also do the metadata handshake and create the peer struct
s.rwc = rwc
// TODO call something to do the metadata exchange
}
// writeMsg writes a message with stream padding, and is *not* thread safe.
func (s *stream) writeMsg(bs []byte) (int, error) {
buf := util.GetBytes()
defer util.PutBytes(buf)
buf = append(buf, streamMsg[:]...)
buf = append(buf, wire_encode_uint64(uint64(len(bs)))...)
padLen := len(buf)
buf = append(buf, bs...)
var bn int
for bn < len(buf) {
n, err := s.rwc.Write(buf[bn:])
bn += n
if err != nil {
l := bn - padLen
if l < 0 {
l = 0
}
return l, err
}
}
return len(bs), nil
}
// readMsg reads a message from the stream, accounting for stream padding, and is *not* thread safe.
func (s *stream) readMsg() ([]byte, error) {
for {
buf := s.inputBuffer
msg, ok, err := stream_chopMsg(&buf)
switch {
case err != nil:
// Something in the stream format is corrupt
return nil, fmt.Errorf("message error: %v", err)
case ok:
// Copy the packet into bs, shift the buffer, and return
msg = append(util.GetBytes(), msg...)
s.inputBuffer = append(s.inputBuffer[:0], buf...)
return msg, nil
default:
// Wait for the underlying reader to return enough info for us to proceed
frag := make([]byte, 2*streamMsgSize)
n, err := s.rwc.Read(frag)
if n > 0 {
s.inputBuffer = append(s.inputBuffer, frag[:n]...)
} else if err != nil {
return nil, err
}
}
}
}
// Writes metadata bytes without stream padding, meant to be temporary
func (s *stream) _sendMetaBytes(metaBytes []byte) error {
var written int
for written < len(metaBytes) {
n, err := s.rwc.Write(metaBytes)
written += n
if err != nil {
return err
}
}
return nil
}
// Reads metadata bytes without stream padding, meant to be temporary
func (s *stream) _recvMetaBytes() ([]byte, error) {
var meta version_metadata
frag := meta.encode()
metaBytes := make([]byte, 0, len(frag))
for len(metaBytes) < len(frag) {
n, err := s.rwc.Read(frag)
if err != nil {
return nil, err
}
metaBytes = append(metaBytes, frag[:n]...)
}
return metaBytes, nil
}
// This takes a pointer to a slice as an argument. It checks if there's a
// complete message and, if so, slices out those parts and returns the message,
// true, and nil. If there's no error, but also no complete message, it returns
// nil, false, and nil. If there's an error, it returns nil, false, and the
// error, which the reader then handles (currently, by returning from the
// reader, which causes the connection to close).
func stream_chopMsg(bs *[]byte) ([]byte, bool, error) {
// Returns msg, ok, err
if len(*bs) < len(streamMsg) {
return nil, false, nil
}
for idx := range streamMsg {
if (*bs)[idx] != streamMsg[idx] {
return nil, false, errors.New("bad message")
}
}
msgLen, msgLenLen := wire_decode_uint64((*bs)[len(streamMsg):])
if msgLen > streamMsgSize {
return nil, false, errors.New("oversized message")
}
msgBegin := len(streamMsg) + msgLenLen
msgEnd := msgBegin + int(msgLen)
if msgLenLen == 0 || len(*bs) < msgEnd {
// We don't have the full message
// Need to buffer this and wait for the rest to come in
return nil, false, nil
}
msg := (*bs)[msgBegin:msgEnd]
(*bs) = (*bs)[msgEnd:]
return msg, true, nil
}

View File

@ -281,6 +281,7 @@ func (t *switchTable) cleanPeers() {
if now.Sub(peer.time) > switch_timeout+switch_throttle { if now.Sub(peer.time) > switch_timeout+switch_throttle {
// Longer than switch_timeout to make sure we don't remove a working peer because the root stopped responding. // Longer than switch_timeout to make sure we don't remove a working peer because the root stopped responding.
delete(t.data.peers, port) delete(t.data.peers, port)
go t.core.peers.removePeer(port) // TODO figure out if it's safe to do this without a goroutine, or make it safe
} }
} }
if _, isIn := t.data.peers[t.parent]; !isIn { if _, isIn := t.data.peers[t.parent]; !isIn {

View File

@ -16,34 +16,28 @@ package yggdrasil
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io"
"math/rand" "math/rand"
"net" "net"
"sync" "sync"
"sync/atomic"
"time" "time"
"golang.org/x/net/proxy" "golang.org/x/net/proxy"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
) )
const tcp_msgSize = 2048 + 65535 // TODO figure out what makes sense const default_timeout = 6 * time.Second
const default_tcp_timeout = 6 * time.Second const tcp_ping_interval = (default_timeout * 2 / 3)
const tcp_ping_interval = (default_tcp_timeout * 2 / 3)
// The TCP listener and information about active TCP connections, to avoid duplication. // The TCP listener and information about active TCP connections, to avoid duplication.
type tcpInterface struct { type tcpInterface struct {
core *Core core *Core
reconfigure chan chan error reconfigure chan chan error
serv net.Listener serv net.Listener
serv_stop chan bool stop chan bool
tcp_timeout time.Duration timeout time.Duration
tcp_addr string addr string
mutex sync.Mutex // Protecting the below mutex sync.Mutex // Protecting the below
calls map[string]struct{} calls map[string]struct{}
conns map[tcpInfo](chan struct{}) conns map[tcpInfo](chan struct{})
@ -86,7 +80,7 @@ func (iface *tcpInterface) connectSOCKS(socksaddr, peeraddr string) {
// Initializes the struct. // Initializes the struct.
func (iface *tcpInterface) init(core *Core) (err error) { func (iface *tcpInterface) init(core *Core) (err error) {
iface.core = core iface.core = core
iface.serv_stop = make(chan bool, 1) iface.stop = make(chan bool, 1)
iface.reconfigure = make(chan chan error, 1) iface.reconfigure = make(chan chan error, 1)
go func() { go func() {
for { for {
@ -95,7 +89,7 @@ func (iface *tcpInterface) init(core *Core) (err error) {
updated := iface.core.config.Listen != iface.core.configOld.Listen updated := iface.core.config.Listen != iface.core.configOld.Listen
iface.core.configMutex.RUnlock() iface.core.configMutex.RUnlock()
if updated { if updated {
iface.serv_stop <- true iface.stop <- true
iface.serv.Close() iface.serv.Close()
e <- iface.listen() e <- iface.listen()
} else { } else {
@ -111,19 +105,19 @@ func (iface *tcpInterface) listen() error {
var err error var err error
iface.core.configMutex.RLock() iface.core.configMutex.RLock()
iface.tcp_addr = iface.core.config.Listen iface.addr = iface.core.config.Listen
iface.tcp_timeout = time.Duration(iface.core.config.ReadTimeout) * time.Millisecond iface.timeout = time.Duration(iface.core.config.ReadTimeout) * time.Millisecond
iface.core.configMutex.RUnlock() iface.core.configMutex.RUnlock()
if iface.tcp_timeout >= 0 && iface.tcp_timeout < default_tcp_timeout { if iface.timeout >= 0 && iface.timeout < default_timeout {
iface.tcp_timeout = default_tcp_timeout iface.timeout = default_timeout
} }
ctx := context.Background() ctx := context.Background()
lc := net.ListenConfig{ lc := net.ListenConfig{
Control: iface.tcpContext, Control: iface.tcpContext,
} }
iface.serv, err = lc.Listen(ctx, "tcp", iface.tcp_addr) iface.serv, err = lc.Listen(ctx, "tcp", iface.addr)
if err == nil { if err == nil {
iface.mutex.Lock() iface.mutex.Lock()
iface.calls = make(map[string]struct{}) iface.calls = make(map[string]struct{})
@ -147,7 +141,7 @@ func (iface *tcpInterface) listener() {
return return
} }
select { select {
case <-iface.serv_stop: case <-iface.stop:
iface.core.log.Errorln("Stopping listener") iface.core.log.Errorln("Stopping listener")
return return
default: default:
@ -186,7 +180,7 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
if sintf != "" { if sintf != "" {
callname = fmt.Sprintf("%s/%s", saddr, sintf) callname = fmt.Sprintf("%s/%s", saddr, sintf)
} }
if iface.isAlreadyCalling(saddr) { if iface.isAlreadyCalling(callname) {
return return
} }
iface.mutex.Lock() iface.mutex.Lock()
@ -194,7 +188,7 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
iface.mutex.Unlock() iface.mutex.Unlock()
defer func() { defer func() {
// Block new calls for a little while, to mitigate livelock scenarios // Block new calls for a little while, to mitigate livelock scenarios
time.Sleep(default_tcp_timeout) time.Sleep(default_timeout)
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
iface.mutex.Lock() iface.mutex.Lock()
delete(iface.calls, callname) delete(iface.calls, callname)
@ -283,245 +277,21 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
}() }()
} }
// This exchanges/checks connection metadata, sets up the peer struct, sets up the writer goroutine, and then runs the reader within the current goroutine.
// It defers a bunch of cleanup stuff to tear down all of these things when the reader exists (e.g. due to a closed connection or a timeout).
func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { func (iface *tcpInterface) handler(sock net.Conn, incoming bool) {
defer sock.Close() defer sock.Close()
iface.setExtraOptions(sock) iface.setExtraOptions(sock)
// Get our keys stream := stream{}
myLinkPub, myLinkPriv := crypto.NewBoxKeys() // ephemeral link keys stream.init(sock)
meta := version_getBaseMetadata() local, _, _ := net.SplitHostPort(sock.LocalAddr().String())
meta.box = iface.core.boxPub remote, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
meta.sig = iface.core.sigPub remotelinklocal := net.ParseIP(remote).IsLinkLocalUnicast()
meta.link = *myLinkPub name := "tcp://" + sock.RemoteAddr().String()
metaBytes := meta.encode() link, err := iface.core.link.create(&stream, name, "tcp", local, remote, incoming, remotelinklocal)
_, err := sock.Write(metaBytes)
if err != nil { if err != nil {
return iface.core.log.Println(err)
panic(err)
} }
if iface.tcp_timeout > 0 { iface.core.log.Debugln("DEBUG: starting handler for", name)
sock.SetReadDeadline(time.Now().Add(iface.tcp_timeout)) err = link.handler()
} iface.core.log.Debugln("DEBUG: stopped handler for", name, err)
_, err = sock.Read(metaBytes)
if err != nil {
return
}
meta = version_metadata{} // Reset to zero value
if !meta.decode(metaBytes) || !meta.check() {
// Failed to decode and check the metadata
// If it's a version mismatch issue, then print an error message
base := version_getBaseMetadata()
if meta.meta == base.meta {
if meta.ver > base.ver {
iface.core.log.Errorln("Failed to connect to node:", sock.RemoteAddr().String(), "version:", meta.ver)
} else if meta.ver == base.ver && meta.minorVer > base.minorVer {
iface.core.log.Errorln("Failed to connect to node:", sock.RemoteAddr().String(), "version:", fmt.Sprintf("%d.%d", meta.ver, meta.minorVer))
}
}
// TODO? Block forever to prevent future connection attempts? suppress future messages about the same node?
return
}
remoteAddr, _, e1 := net.SplitHostPort(sock.RemoteAddr().String())
localAddr, _, e2 := net.SplitHostPort(sock.LocalAddr().String())
if e1 != nil || e2 != nil {
return
}
info := tcpInfo{ // used as a map key, so don't include ephemeral link key
box: meta.box,
sig: meta.sig,
localAddr: localAddr,
remoteAddr: remoteAddr,
}
if iface.isAlreadyConnected(info) {
return
}
// Quit the parent call if this is a connection to ourself
equiv := func(k1, k2 []byte) bool {
for idx := range k1 {
if k1[idx] != k2[idx] {
return false
}
}
return true
}
if equiv(meta.box[:], iface.core.boxPub[:]) {
return
}
if equiv(meta.sig[:], iface.core.sigPub[:]) {
return
}
// Check if we're authorized to connect to this key / IP
if incoming && !iface.core.peers.isAllowedEncryptionPublicKey(&meta.box) {
// Allow unauthorized peers if they're link-local
raddrStr, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
raddr := net.ParseIP(raddrStr)
if !raddr.IsLinkLocalUnicast() {
return
}
}
// Check if we already have a connection to this node, close and block if yes
iface.mutex.Lock()
/*if blockChan, isIn := iface.conns[info]; isIn {
iface.mutex.Unlock()
sock.Close()
<-blockChan
return
}*/
blockChan := make(chan struct{})
iface.conns[info] = blockChan
iface.mutex.Unlock()
defer func() {
iface.mutex.Lock()
delete(iface.conns, info)
iface.mutex.Unlock()
close(blockChan)
}()
// Note that multiple connections to the same node are allowed
// E.g. over different interfaces
p := iface.core.peers.newPeer(&meta.box, &meta.sig, crypto.GetSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String())
p.linkOut = make(chan []byte, 1)
in := func(bs []byte) {
p.handlePacket(bs)
}
out := make(chan []byte, 1)
defer close(out)
go func() {
// This goroutine waits for outgoing packets, link protocol traffic, or sends idle keep-alive traffic
send := func(msg []byte) {
msgLen := wire_encode_uint64(uint64(len(msg)))
buf := net.Buffers{tcp_msg[:], msgLen, msg}
buf.WriteTo(sock)
atomic.AddUint64(&p.bytesSent, uint64(len(tcp_msg)+len(msgLen)+len(msg)))
util.PutBytes(msg)
}
timerInterval := tcp_ping_interval
timer := time.NewTimer(timerInterval)
defer timer.Stop()
for {
select {
case msg := <-p.linkOut:
// Always send outgoing link traffic first, if needed
send(msg)
continue
default:
}
// Otherwise wait reset the timer and wait for something to do
timer.Stop()
select {
case <-timer.C:
default:
}
timer.Reset(timerInterval)
select {
case _ = <-timer.C:
send(nil) // TCP keep-alive traffic
case msg := <-p.linkOut:
send(msg)
case msg, ok := <-out:
if !ok {
return
}
send(msg) // Block until the socket write has finished
// Now inform the switch that we're ready for more traffic
p.core.switchTable.idleIn <- p.port
}
}
}()
p.core.switchTable.idleIn <- p.port // Start in the idle state
p.out = func(msg []byte) {
defer func() { recover() }()
out <- msg
}
p.close = func() { sock.Close() }
go p.linkLoop()
defer func() {
// Put all of our cleanup here...
p.core.peers.removePeer(p.port)
}()
us, _, _ := net.SplitHostPort(sock.LocalAddr().String())
them, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
themNodeID := crypto.GetNodeID(&meta.box)
themAddr := address.AddrForNodeID(themNodeID)
themAddrString := net.IP(themAddr[:]).String()
themString := fmt.Sprintf("%s@%s", themAddrString, them)
iface.core.log.Infof("Connected: %s, source: %s", themString, us)
err = iface.reader(sock, in) // In this goroutine, because of defers
if err == nil {
iface.core.log.Infof("Disconnected: %s, source: %s", themString, us)
} else {
iface.core.log.Infof("Disconnected: %s, source: %s, error: %s", themString, us, err)
}
return
}
// This reads from the socket into a []byte buffer for incomping messages.
// It copies completed messages out of the cache into a new slice, and passes them to the peer struct via the provided `in func([]byte)` argument.
// Then it shifts the incomplete fragments of data forward so future reads won't overwrite it.
func (iface *tcpInterface) reader(sock net.Conn, in func([]byte)) error {
bs := make([]byte, 2*tcp_msgSize)
frag := bs[:0]
for {
if iface.tcp_timeout > 0 {
sock.SetReadDeadline(time.Now().Add(iface.tcp_timeout))
}
n, err := sock.Read(bs[len(frag):])
if n > 0 {
frag = bs[:len(frag)+n]
for {
msg, ok, err2 := tcp_chop_msg(&frag)
if err2 != nil {
return fmt.Errorf("Message error: %v", err2)
}
if !ok {
// We didn't get the whole message yet
break
}
newMsg := append(util.GetBytes(), msg...)
in(newMsg)
util.Yield()
}
frag = append(bs[:0], frag...)
}
if err != nil || n == 0 {
if err != io.EOF {
return err
}
return nil
}
}
}
////////////////////////////////////////////////////////////////////////////////
// These are 4 bytes of padding used to catch if something went horribly wrong with the tcp connection.
var tcp_msg = [...]byte{0xde, 0xad, 0xb1, 0x75} // "dead bits"
// This takes a pointer to a slice as an argument.
// It checks if there's a complete message and, if so, slices out those parts and returns the message, true, and nil.
// If there's no error, but also no complete message, it returns nil, false, and nil.
// If there's an error, it returns nil, false, and the error, which the reader then handles (currently, by returning from the reader, which causes the connection to close).
func tcp_chop_msg(bs *[]byte) ([]byte, bool, error) {
// Returns msg, ok, err
if len(*bs) < len(tcp_msg) {
return nil, false, nil
}
for idx := range tcp_msg {
if (*bs)[idx] != tcp_msg[idx] {
return nil, false, errors.New("Bad message!")
}
}
msgLen, msgLenLen := wire_decode_uint64((*bs)[len(tcp_msg):])
if msgLen > tcp_msgSize {
return nil, false, errors.New("Oversized message!")
}
msgBegin := len(tcp_msg) + msgLenLen
msgEnd := msgBegin + int(msgLen)
if msgLenLen == 0 || len(*bs) < msgEnd {
// We don't have the full message
// Need to buffer this and wait for the rest to come in
return nil, false, nil
}
msg := (*bs)[msgBegin:msgEnd]
(*bs) = (*bs)[msgEnd:]
return msg, true, nil
} }