Merge pull request #519 from Arceliar/actors

Actors
This commit is contained in:
Arceliar 2019-08-31 12:02:50 -05:00 committed by GitHub
commit 5c0f79c4ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1455 additions and 2254 deletions

View File

@ -279,6 +279,7 @@ func main() {
case _ = <-r: case _ = <-r:
if *useconffile != "" { if *useconffile != "" {
cfg = readConfig(useconf, useconffile, normaliseconf) cfg = readConfig(useconf, useconffile, normaliseconf)
logger.Infoln("Reloading configuration from", *useconffile)
n.core.UpdateConfig(cfg) n.core.UpdateConfig(cfg)
n.tuntap.UpdateConfig(cfg) n.tuntap.UpdateConfig(cfg)
n.multicast.UpdateConfig(cfg) n.multicast.UpdateConfig(cfg)

1
go.mod
View File

@ -1,6 +1,7 @@
module github.com/yggdrasil-network/yggdrasil-go module github.com/yggdrasil-network/yggdrasil-go
require ( require (
github.com/Arceliar/phony v0.0.0-20190831050304-94a6d3da9ba4
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8
github.com/hashicorp/go-syslog v1.0.0 github.com/hashicorp/go-syslog v1.0.0
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222

2
go.sum
View File

@ -1,3 +1,5 @@
github.com/Arceliar/phony v0.0.0-20190831050304-94a6d3da9ba4 h1:OePImnPRBqS6JiHuVVq4Rfvt2yyhqMpWCB63PrwGrJE=
github.com/Arceliar/phony v0.0.0-20190831050304-94a6d3da9ba4/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY= 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/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=

View File

@ -83,7 +83,6 @@ func (m *Multicast) Stop() error {
func (m *Multicast) UpdateConfig(config *config.NodeConfig) { func (m *Multicast) UpdateConfig(config *config.NodeConfig) {
m.log.Debugln("Reloading multicast configuration...") m.log.Debugln("Reloading multicast configuration...")
m.config.Replace(*config) m.config.Replace(*config)
m.log.Infoln("Multicast configuration reloaded successfully")
} }
// GetInterfaces returns the currently known/enabled multicast interfaces. It is // GetInterfaces returns the currently known/enabled multicast interfaces. It is

View File

@ -20,7 +20,6 @@ import (
type cryptokey struct { type cryptokey struct {
tun *TunAdapter tun *TunAdapter
enabled atomic.Value // bool enabled atomic.Value // bool
reconfigure chan chan error
ipv4remotes []cryptokey_route ipv4remotes []cryptokey_route
ipv6remotes []cryptokey_route ipv6remotes []cryptokey_route
ipv4cache map[address.Address]cryptokey_route ipv4cache map[address.Address]cryptokey_route
@ -40,25 +39,11 @@ type cryptokey_route struct {
// Initialise crypto-key routing. This must be done before any other CKR calls. // Initialise crypto-key routing. This must be done before any other CKR calls.
func (c *cryptokey) init(tun *TunAdapter) { func (c *cryptokey) init(tun *TunAdapter) {
c.tun = tun c.tun = tun
c.reconfigure = make(chan chan error, 1) c.configure()
go func() {
for {
e := <-c.reconfigure
e <- nil
}
}()
c.tun.log.Debugln("Configuring CKR...")
if err := c.configure(); err != nil {
c.tun.log.Errorln("CKR configuration failed:", err)
} else {
c.tun.log.Debugln("CKR configured")
}
} }
// Configure the CKR routes - this must only ever be called from the router // Configure the CKR routes. This should only ever be ran by the TUN/TAP actor.
// goroutine, e.g. through router.doAdmin func (c *cryptokey) configure() {
func (c *cryptokey) configure() error {
current := c.tun.config.GetCurrent() current := c.tun.config.GetCurrent()
// Set enabled/disabled state // Set enabled/disabled state
@ -73,14 +58,14 @@ func (c *cryptokey) configure() error {
// Add IPv6 routes // Add IPv6 routes
for ipv6, pubkey := range current.TunnelRouting.IPv6RemoteSubnets { for ipv6, pubkey := range current.TunnelRouting.IPv6RemoteSubnets {
if err := c.addRemoteSubnet(ipv6, pubkey); err != nil { if err := c.addRemoteSubnet(ipv6, pubkey); err != nil {
return err c.tun.log.Errorln("Error adding CKR IPv6 remote subnet:", err)
} }
} }
// Add IPv4 routes // Add IPv4 routes
for ipv4, pubkey := range current.TunnelRouting.IPv4RemoteSubnets { for ipv4, pubkey := range current.TunnelRouting.IPv4RemoteSubnets {
if err := c.addRemoteSubnet(ipv4, pubkey); err != nil { if err := c.addRemoteSubnet(ipv4, pubkey); err != nil {
return err c.tun.log.Errorln("Error adding CKR IPv4 remote subnet:", err)
} }
} }
@ -94,7 +79,7 @@ func (c *cryptokey) configure() error {
c.ipv6locals = make([]net.IPNet, 0) c.ipv6locals = make([]net.IPNet, 0)
for _, source := range current.TunnelRouting.IPv6LocalSubnets { for _, source := range current.TunnelRouting.IPv6LocalSubnets {
if err := c.addLocalSubnet(source); err != nil { if err := c.addLocalSubnet(source); err != nil {
return err c.tun.log.Errorln("Error adding CKR IPv6 local subnet:", err)
} }
} }
@ -102,7 +87,7 @@ func (c *cryptokey) configure() error {
c.ipv4locals = make([]net.IPNet, 0) c.ipv4locals = make([]net.IPNet, 0)
for _, source := range current.TunnelRouting.IPv4LocalSubnets { for _, source := range current.TunnelRouting.IPv4LocalSubnets {
if err := c.addLocalSubnet(source); err != nil { if err := c.addLocalSubnet(source); err != nil {
return err c.tun.log.Errorln("Error adding CKR IPv4 local subnet:", err)
} }
} }
@ -111,8 +96,6 @@ func (c *cryptokey) configure() error {
c.ipv4cache = make(map[address.Address]cryptokey_route, 0) c.ipv4cache = make(map[address.Address]cryptokey_route, 0)
c.ipv6cache = make(map[address.Address]cryptokey_route, 0) c.ipv6cache = make(map[address.Address]cryptokey_route, 0)
c.mutexcaches.Unlock() c.mutexcaches.Unlock()
return nil
} }
// Enable or disable crypto-key routing. // Enable or disable crypto-key routing.
@ -182,19 +165,19 @@ func (c *cryptokey) addLocalSubnet(cidr string) error {
} else if prefixsize == net.IPv4len*8 { } else if prefixsize == net.IPv4len*8 {
routingsources = &c.ipv4locals routingsources = &c.ipv4locals
} else { } else {
return errors.New("Unexpected prefix size") return errors.New("unexpected prefix size")
} }
// Check if we already have this CIDR // Check if we already have this CIDR
for _, subnet := range *routingsources { for _, subnet := range *routingsources {
if subnet.String() == ipnet.String() { if subnet.String() == ipnet.String() {
return errors.New("Source subnet already configured") return errors.New("local subnet already configured")
} }
} }
// Add the source subnet // Add the source subnet
*routingsources = append(*routingsources, *ipnet) *routingsources = append(*routingsources, *ipnet)
c.tun.log.Infoln("Added CKR source subnet", cidr) c.tun.log.Infoln("Added CKR local subnet", cidr)
return nil return nil
} }
@ -227,7 +210,7 @@ func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error {
routingtable = &c.ipv4remotes routingtable = &c.ipv4remotes
routingcache = &c.ipv4cache routingcache = &c.ipv4cache
} else { } else {
return errors.New("Unexpected prefix size") return errors.New("unexpected prefix size")
} }
// Is the route an Yggdrasil destination? // Is the route an Yggdrasil destination?
@ -236,19 +219,19 @@ func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error {
copy(addr[:], ipaddr) copy(addr[:], ipaddr)
copy(snet[:], ipnet.IP) copy(snet[:], ipnet.IP)
if addr.IsValid() || snet.IsValid() { if addr.IsValid() || snet.IsValid() {
return errors.New("Can't specify Yggdrasil destination as crypto-key route") return errors.New("can't specify Yggdrasil destination as crypto-key route")
} }
// Do we already have a route for this subnet? // Do we already have a route for this subnet?
for _, route := range *routingtable { for _, route := range *routingtable {
if route.subnet.String() == ipnet.String() { if route.subnet.String() == ipnet.String() {
return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) return fmt.Errorf("remote subnet already exists for %s", cidr)
} }
} }
// Decode the public key // Decode the public key
if bpk, err := hex.DecodeString(dest); err != nil { if bpk, err := hex.DecodeString(dest); err != nil {
return err return err
} else if len(bpk) != crypto.BoxPubKeyLen { } else if len(bpk) != crypto.BoxPubKeyLen {
return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) return fmt.Errorf("incorrect key length for %s", dest)
} else { } else {
// Add the new crypto-key route // Add the new crypto-key route
var key crypto.BoxPubKey var key crypto.BoxPubKey
@ -271,7 +254,7 @@ func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error {
delete(*routingcache, k) delete(*routingcache, k)
} }
c.tun.log.Infoln("Added CKR destination subnet", cidr) c.tun.log.Infoln("Added CKR remote subnet", cidr)
return nil return nil
} }
} }
@ -285,7 +268,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
// Check if the address is a valid Yggdrasil address - if so it // Check if the address is a valid Yggdrasil address - if so it
// is exempt from all CKR checking // is exempt from all CKR checking
if addr.IsValid() { if addr.IsValid() {
return crypto.BoxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses") return crypto.BoxPubKey{}, errors.New("cannot look up CKR for Yggdrasil addresses")
} }
// Build our references to the routing table and cache // Build our references to the routing table and cache
@ -298,7 +281,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
} else if addrlen == net.IPv4len { } else if addrlen == net.IPv4len {
routingcache = &c.ipv4cache routingcache = &c.ipv4cache
} else { } else {
return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") return crypto.BoxPubKey{}, errors.New("unexpected prefix size")
} }
// Check if there's a cache entry for this addr // Check if there's a cache entry for this addr
@ -318,7 +301,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
} else if addrlen == net.IPv4len { } else if addrlen == net.IPv4len {
routingtable = &c.ipv4remotes routingtable = &c.ipv4remotes
} else { } else {
return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") return crypto.BoxPubKey{}, errors.New("unexpected prefix size")
} }
// No cache was found - start by converting the address into a net.IP // No cache was found - start by converting the address into a net.IP
@ -379,18 +362,18 @@ func (c *cryptokey) removeLocalSubnet(cidr string) error {
} else if prefixsize == net.IPv4len*8 { } else if prefixsize == net.IPv4len*8 {
routingsources = &c.ipv4locals routingsources = &c.ipv4locals
} else { } else {
return errors.New("Unexpected prefix size") return errors.New("unexpected prefix size")
} }
// Check if we already have this CIDR // Check if we already have this CIDR
for idx, subnet := range *routingsources { for idx, subnet := range *routingsources {
if subnet.String() == ipnet.String() { if subnet.String() == ipnet.String() {
*routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...) *routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...)
c.tun.log.Infoln("Removed CKR source subnet", cidr) c.tun.log.Infoln("Removed CKR local subnet", cidr)
return nil return nil
} }
} }
return errors.New("Source subnet not found") return errors.New("local subnet not found")
} }
// Removes a destination route for the given CIDR to be tunnelled to the node // Removes a destination route for the given CIDR to be tunnelled to the node
@ -422,7 +405,7 @@ func (c *cryptokey) removeRemoteSubnet(cidr string, dest string) error {
routingtable = &c.ipv4remotes routingtable = &c.ipv4remotes
routingcache = &c.ipv4cache routingcache = &c.ipv4cache
} else { } else {
return errors.New("Unexpected prefix size") return errors.New("unexpected prefix size")
} }
// Decode the public key // Decode the public key
@ -430,7 +413,7 @@ func (c *cryptokey) removeRemoteSubnet(cidr string, dest string) error {
if err != nil { if err != nil {
return err return err
} else if len(bpk) != crypto.BoxPubKeyLen { } else if len(bpk) != crypto.BoxPubKeyLen {
return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) return fmt.Errorf("incorrect key length for %s", dest)
} }
netStr := ipnet.String() netStr := ipnet.String()
@ -440,9 +423,9 @@ func (c *cryptokey) removeRemoteSubnet(cidr string, dest string) error {
for k := range *routingcache { for k := range *routingcache {
delete(*routingcache, k) delete(*routingcache, k)
} }
c.tun.log.Infof("Removed CKR destination subnet %s via %s\n", cidr, dest) c.tun.log.Infof("Removed CKR remote subnet %s via %s\n", cidr, dest)
return nil return nil
} }
} }
return errors.New(fmt.Sprintf("Route does not exists for %s", cidr)) return fmt.Errorf("route does not exists for %s", cidr)
} }

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"time" "time"
"github.com/Arceliar/phony"
"github.com/yggdrasil-network/yggdrasil-go/src/address" "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" "github.com/yggdrasil-network/yggdrasil-go/src/util"
@ -16,22 +17,20 @@ import (
const tunConnTimeout = 2 * time.Minute const tunConnTimeout = 2 * time.Minute
type tunConn struct { type tunConn struct {
phony.Inbox
tun *TunAdapter tun *TunAdapter
conn *yggdrasil.Conn conn *yggdrasil.Conn
addr address.Address addr address.Address
snet address.Subnet snet address.Subnet
send chan []byte
stop chan struct{} stop chan struct{}
alive chan struct{} alive *time.Timer // From calling time.AfterFunc
} }
func (s *tunConn) close() { func (s *tunConn) close() {
s.tun.mutex.Lock() s.tun.Act(s, s._close_from_tun)
defer s.tun.mutex.Unlock()
s._close_nomutex()
} }
func (s *tunConn) _close_nomutex() { func (s *tunConn) _close_from_tun() {
s.conn.Close() s.conn.Close()
delete(s.tun.addrToConn, s.addr) delete(s.tun.addrToConn, s.addr)
delete(s.tun.subnetToConn, s.snet) delete(s.tun.subnetToConn, s.snet)
@ -39,235 +38,197 @@ func (s *tunConn) _close_nomutex() {
defer func() { recover() }() defer func() { recover() }()
close(s.stop) // Closes reader/writer goroutines close(s.stop) // Closes reader/writer goroutines
}() }()
func() {
defer func() { recover() }()
close(s.alive) // Closes timeout goroutine
}()
} }
func (s *tunConn) reader() (err error) { func (s *tunConn) _read(bs []byte) (err error) {
select { select {
case _, ok := <-s.stop: case <-s.stop:
if !ok { err = errors.New("session was already closed")
return errors.New("session was already closed") util.PutBytes(bs)
} return
default: default:
} }
s.tun.log.Debugln("Starting conn reader for", s.conn.String()) if len(bs) == 0 {
defer s.tun.log.Debugln("Stopping conn reader for", s.conn.String()) err = errors.New("read packet with 0 size")
for { util.PutBytes(bs)
select { return
case <-s.stop: }
return nil ipv4 := len(bs) > 20 && bs[0]&0xf0 == 0x40
default: ipv6 := len(bs) > 40 && bs[0]&0xf0 == 0x60
isCGA := true
// Check source addresses
switch {
case ipv6 && bs[8] == 0x02 && bytes.Equal(s.addr[:16], bs[8:24]): // source
case ipv6 && bs[8] == 0x03 && bytes.Equal(s.snet[:8], bs[8:16]): // source
default:
isCGA = false
}
// Check destiantion addresses
switch {
case ipv6 && bs[24] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[24:40]): // destination
case ipv6 && bs[24] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[24:32]): // destination
default:
isCGA = false
}
// Decide how to handle the packet
var skip bool
switch {
case isCGA: // Allowed
case s.tun.ckr.isEnabled() && (ipv4 || ipv6):
var srcAddr address.Address
var dstAddr address.Address
var addrlen int
if ipv4 {
copy(srcAddr[:], bs[12:16])
copy(dstAddr[:], bs[16:20])
addrlen = 4
} }
var bs []byte if ipv6 {
if bs, err = s.conn.ReadNoCopy(); err != nil { copy(srcAddr[:], bs[8:24])
if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() { copy(dstAddr[:], bs[24:40])
if e.Closed() { addrlen = 16
s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn read debug:", err) }
} else { if !s.tun.ckr.isValidLocalAddress(dstAddr, addrlen) {
s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) // The destination address isn't in our CKR allowed range
} skip = true
return e } else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil {
} srcNodeID := crypto.GetNodeID(&key)
} else if len(bs) > 0 { if s.conn.RemoteAddr() == *srcNodeID {
ipv4 := len(bs) > 20 && bs[0]&0xf0 == 0x40 // This is the one allowed CKR case, where source and destination addresses are both good
ipv6 := len(bs) > 40 && bs[0]&0xf0 == 0x60 } else {
isCGA := true // The CKR key associated with this address doesn't match the sender's NodeID
// Check source addresses
switch {
case ipv6 && bs[8] == 0x02 && bytes.Equal(s.addr[:16], bs[8:24]): // source
case ipv6 && bs[8] == 0x03 && bytes.Equal(s.snet[:8], bs[8:16]): // source
default:
isCGA = false
}
// Check destiantion addresses
switch {
case ipv6 && bs[24] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[24:40]): // destination
case ipv6 && bs[24] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[24:32]): // destination
default:
isCGA = false
}
// Decide how to handle the packet
var skip bool
switch {
case isCGA: // Allowed
case s.tun.ckr.isEnabled() && (ipv4 || ipv6):
var srcAddr address.Address
var dstAddr address.Address
var addrlen int
if ipv4 {
copy(srcAddr[:], bs[12:16])
copy(dstAddr[:], bs[16:20])
addrlen = 4
}
if ipv6 {
copy(srcAddr[:], bs[8:24])
copy(dstAddr[:], bs[24:40])
addrlen = 16
}
if !s.tun.ckr.isValidLocalAddress(dstAddr, addrlen) {
// The destination address isn't in our CKR allowed range
skip = true
} else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil {
srcNodeID := crypto.GetNodeID(&key)
if s.conn.RemoteAddr() == *srcNodeID {
// This is the one allowed CKR case, where source and destination addresses are both good
} else {
// The CKR key associated with this address doesn't match the sender's NodeID
skip = true
}
} else {
// We have no CKR route for this source address
skip = true
}
default:
skip = true skip = true
} }
if skip {
util.PutBytes(bs)
continue
}
s.tun.send <- bs
s.stillAlive()
} else { } else {
util.PutBytes(bs) // We have no CKR route for this source address
} skip = true
}
}
func (s *tunConn) writer() error {
select {
case _, ok := <-s.stop:
if !ok {
return errors.New("session was already closed")
} }
default: default:
skip = true
} }
s.tun.log.Debugln("Starting conn writer for", s.conn.String()) if skip {
defer s.tun.log.Debugln("Stopping conn writer for", s.conn.String()) err = errors.New("address not allowed")
for { util.PutBytes(bs)
select { return
case <-s.stop: }
return nil s.tun.writer.writeFrom(s, bs)
case bs, ok := <-s.send: s.stillAlive()
if !ok { return
return errors.New("send closed") }
}
v4 := len(bs) > 20 && bs[0]&0xf0 == 0x40 func (s *tunConn) writeFrom(from phony.Actor, bs []byte) {
v6 := len(bs) > 40 && bs[0]&0xf0 == 0x60 s.Act(from, func() {
isCGA := true s._write(bs)
// Check source addresses })
switch { }
case v6 && bs[8] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[8:24]): // source
case v6 && bs[8] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[8:16]): // source func (s *tunConn) _write(bs []byte) (err error) {
default: select {
isCGA = false case <-s.stop:
} err = errors.New("session was already closed")
// Check destiantion addresses util.PutBytes(bs)
switch { return
case v6 && bs[24] == 0x02 && bytes.Equal(s.addr[:16], bs[24:40]): // destination default:
case v6 && bs[24] == 0x03 && bytes.Equal(s.snet[:8], bs[24:32]): // destination }
default: v4 := len(bs) > 20 && bs[0]&0xf0 == 0x40
isCGA = false v6 := len(bs) > 40 && bs[0]&0xf0 == 0x60
} isCGA := true
// Decide how to handle the packet // Check source addresses
var skip bool switch {
switch { case v6 && bs[8] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[8:24]): // source
case isCGA: // Allowed case v6 && bs[8] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[8:16]): // source
case s.tun.ckr.isEnabled() && (v4 || v6): default:
var srcAddr address.Address isCGA = false
var dstAddr address.Address }
var addrlen int // Check destiantion addresses
if v4 { switch {
copy(srcAddr[:], bs[12:16]) case v6 && bs[24] == 0x02 && bytes.Equal(s.addr[:16], bs[24:40]): // destination
copy(dstAddr[:], bs[16:20]) case v6 && bs[24] == 0x03 && bytes.Equal(s.snet[:8], bs[24:32]): // destination
addrlen = 4 default:
} isCGA = false
if v6 { }
copy(srcAddr[:], bs[8:24]) // Decide how to handle the packet
copy(dstAddr[:], bs[24:40]) var skip bool
addrlen = 16 switch {
} case isCGA: // Allowed
if !s.tun.ckr.isValidLocalAddress(srcAddr, addrlen) { case s.tun.ckr.isEnabled() && (v4 || v6):
// The source address isn't in our CKR allowed range var srcAddr address.Address
skip = true var dstAddr address.Address
} else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { var addrlen int
dstNodeID := crypto.GetNodeID(&key) if v4 {
if s.conn.RemoteAddr() == *dstNodeID { copy(srcAddr[:], bs[12:16])
// This is the one allowed CKR case, where source and destination addresses are both good copy(dstAddr[:], bs[16:20])
} else { addrlen = 4
// The CKR key associated with this address doesn't match the sender's NodeID }
skip = true if v6 {
} copy(srcAddr[:], bs[8:24])
} else { copy(dstAddr[:], bs[24:40])
// We have no CKR route for this destination address... why do we have the packet in the first place? addrlen = 16
skip = true }
} if !s.tun.ckr.isValidLocalAddress(srcAddr, addrlen) {
default: // The source address isn't in our CKR allowed range
skip = true
} else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil {
dstNodeID := crypto.GetNodeID(&key)
if s.conn.RemoteAddr() == *dstNodeID {
// This is the one allowed CKR case, where source and destination addresses are both good
} else {
// The CKR key associated with this address doesn't match the sender's NodeID
skip = true skip = true
} }
if skip { } else {
util.PutBytes(bs) // We have no CKR route for this destination address... why do we have the packet in the first place?
continue skip = true
} }
msg := yggdrasil.FlowKeyMessage{ default:
FlowKey: util.GetFlowKey(bs), skip = true
Message: bs, }
} if skip {
if err := s.conn.WriteNoCopy(msg); err != nil { err = errors.New("address not allowed")
if e, eok := err.(yggdrasil.ConnError); !eok { util.PutBytes(bs)
if e.Closed() { return
s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err) }
} else { msg := yggdrasil.FlowKeyMessage{
s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err) FlowKey: util.GetFlowKey(bs),
} Message: bs,
} else if e.PacketTooBig() { }
// TODO: This currently isn't aware of IPv4 for CKR s.conn.WriteFrom(s, msg, func(err error) {
ptb := &icmp.PacketTooBig{ if err == nil {
MTU: int(e.PacketMaximumSize()), // No point in wasting resources to send back an error if there was none
Data: bs[:900], return
} }
if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { s.Act(s.conn, func() {
s.tun.send <- packet if e, eok := err.(yggdrasil.ConnError); !eok {
} if e.Closed() {
s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err)
} else { } else {
if e.Closed() { s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err)
s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn write debug:", err) }
} else { } else if e.PacketTooBig() {
s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) // TODO: This currently isn't aware of IPv4 for CKR
} ptb := &icmp.PacketTooBig{
MTU: int(e.PacketMaximumSize()),
Data: bs[:900],
}
if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil {
s.tun.writer.writeFrom(s, packet)
} }
} else { } else {
s.stillAlive() if e.Closed() {
s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn write debug:", err)
} else {
s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err)
}
} }
} })
} })
s.stillAlive()
return
} }
func (s *tunConn) stillAlive() { func (s *tunConn) stillAlive() {
defer func() { recover() }() if s.alive != nil {
select { s.alive.Stop()
case s.alive <- struct{}{}:
default:
}
}
func (s *tunConn) checkForTimeouts() error {
timer := time.NewTimer(tunConnTimeout)
defer util.TimerStop(timer)
defer s.close()
for {
select {
case _, ok := <-s.alive:
if !ok {
return errors.New("connection closed")
}
util.TimerStop(timer)
timer.Reset(tunConnTimeout)
case <-timer.C:
return errors.New("timed out")
}
} }
s.alive = time.AfterFunc(tunConnTimeout, s.close)
} }

View File

@ -9,264 +9,269 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/address" "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" "github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/Arceliar/phony"
) )
func (tun *TunAdapter) writer() error { type tunWriter struct {
var w int phony.Inbox
tun *TunAdapter
}
func (w *tunWriter) writeFrom(from phony.Actor, b []byte) {
w.Act(from, func() {
w._write(b)
})
}
// write is pretty loose with the memory safety rules, e.g. it assumes it can read w.tun.iface.IsTap() safely
func (w *tunWriter) _write(b []byte) {
var written int
var err error var err error
for { n := len(b)
b := <-tun.send if n == 0 {
n := len(b) return
if n == 0 { }
continue if w.tun.iface.IsTAP() {
sendndp := func(dstAddr address.Address) {
neigh, known := w.tun.icmpv6.getNeighbor(dstAddr)
known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30)
if !known {
w.tun.icmpv6.Solicit(dstAddr)
}
} }
if tun.iface.IsTAP() { peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
sendndp := func(dstAddr address.Address) { var dstAddr address.Address
neigh, known := tun.icmpv6.getNeighbor(dstAddr) var peerknown bool
known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) if b[0]&0xf0 == 0x40 {
if !known { dstAddr = w.tun.addr
tun.icmpv6.Solicit(dstAddr) } else if b[0]&0xf0 == 0x60 {
} if !bytes.Equal(w.tun.addr[:16], dstAddr[:16]) && !bytes.Equal(w.tun.subnet[:8], dstAddr[:8]) {
} dstAddr = w.tun.addr
peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
var dstAddr address.Address
var peerknown bool
if b[0]&0xf0 == 0x40 {
dstAddr = tun.addr
} else if b[0]&0xf0 == 0x60 {
if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) {
dstAddr = tun.addr
}
}
if neighbor, ok := tun.icmpv6.getNeighbor(dstAddr); ok && neighbor.learned {
// If we've learned the MAC of a 300::/7 address, for example, or a CKR
// address, use the MAC address of that
peermac = neighbor.mac
peerknown = true
} else if neighbor, ok := tun.icmpv6.getNeighbor(tun.addr); ok && neighbor.learned {
// Otherwise send directly to the MAC address of the host if that's
// known instead
peermac = neighbor.mac
peerknown = true
} else {
// Nothing has been discovered, try to discover the destination
sendndp(tun.addr)
}
if peerknown {
var proto ethernet.Ethertype
switch {
case b[0]&0xf0 == 0x60:
proto = ethernet.IPv6
case b[0]&0xf0 == 0x40:
proto = ethernet.IPv4
}
var frame ethernet.Frame
frame.Prepare(
peermac[:6], // Destination MAC address
tun.icmpv6.mymac[:6], // Source MAC address
ethernet.NotTagged, // VLAN tagging
proto, // Ethertype
len(b)) // Payload length
copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n])
n += tun_ETHER_HEADER_LENGTH
w, err = tun.iface.Write(frame[:n])
} else {
tun.log.Errorln("TUN/TAP iface write error: no peer MAC known for", net.IP(dstAddr[:]).String(), "- dropping packet")
} }
}
if neighbor, ok := w.tun.icmpv6.getNeighbor(dstAddr); ok && neighbor.learned {
// If we've learned the MAC of a 300::/7 address, for example, or a CKR
// address, use the MAC address of that
peermac = neighbor.mac
peerknown = true
} else if neighbor, ok := w.tun.icmpv6.getNeighbor(w.tun.addr); ok && neighbor.learned {
// Otherwise send directly to the MAC address of the host if that's
// known instead
peermac = neighbor.mac
peerknown = true
} else { } else {
w, err = tun.iface.Write(b[:n]) // Nothing has been discovered, try to discover the destination
util.PutBytes(b) sendndp(w.tun.addr)
} }
if err != nil { if peerknown {
if !tun.isOpen { var proto ethernet.Ethertype
return err switch {
case b[0]&0xf0 == 0x60:
proto = ethernet.IPv6
case b[0]&0xf0 == 0x40:
proto = ethernet.IPv4
} }
tun.log.Errorln("TUN/TAP iface write error:", err) var frame ethernet.Frame
continue frame.Prepare(
} peermac[:6], // Destination MAC address
if w != n { w.tun.icmpv6.mymac[:6], // Source MAC address
tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") ethernet.NotTagged, // VLAN tagging
continue proto, // Ethertype
len(b)) // Payload length
copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n])
n += tun_ETHER_HEADER_LENGTH
written, err = w.tun.iface.Write(frame[:n])
} else {
w.tun.log.Errorln("TUN/TAP iface write error: no peer MAC known for", net.IP(dstAddr[:]).String(), "- dropping packet")
} }
} else {
written, err = w.tun.iface.Write(b[:n])
util.PutBytes(b)
}
if err != nil {
w.tun.Act(w, func() {
if !w.tun.isOpen {
w.tun.log.Errorln("TUN/TAP iface write error:", err)
}
})
}
if written != n {
w.tun.log.Errorln("TUN/TAP iface write mismatch:", written, "bytes written vs", n, "bytes given")
} }
} }
// Run in a separate goroutine by the reader type tunReader struct {
// Does all of the per-packet ICMP checks, passes packets to the right Conn worker phony.Inbox
func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { tun *TunAdapter
for recvd := range ch { }
// If it's a TAP adapter, update the buffer slice so that we no longer
// include the ethernet headers func (r *tunReader) _read() {
offset := 0 // Get a slice to store the packet in
if tun.iface.IsTAP() { recvd := util.ResizeBytes(util.GetBytes(), 65535+tun_ETHER_HEADER_LENGTH)
// Set our offset to beyond the ethernet headers // Wait for a packet to be delivered to us through the TUN/TAP adapter
offset = tun_ETHER_HEADER_LENGTH n, err := r.tun.iface.Read(recvd)
// Check first of all that we can go beyond the ethernet headers if n == 0 {
if len(recvd) <= offset { util.PutBytes(recvd)
continue } else {
} r.tun.handlePacketFrom(r, recvd[:n], err)
}
if err == nil {
// Now read again
r.Act(nil, r._read)
}
}
func (tun *TunAdapter) handlePacketFrom(from phony.Actor, packet []byte, err error) {
tun.Act(from, func() {
tun._handlePacket(packet, err)
})
}
// does the work of reading a packet and sending it to the correct tunConn
func (tun *TunAdapter) _handlePacket(recvd []byte, err error) {
if err != nil {
tun.log.Errorln("TUN/TAP iface read error:", err)
return
}
// If it's a TAP adapter, update the buffer slice so that we no longer
// include the ethernet headers
offset := 0
if tun.iface.IsTAP() {
// Set our offset to beyond the ethernet headers
offset = tun_ETHER_HEADER_LENGTH
// Check first of all that we can go beyond the ethernet headers
if len(recvd) <= offset {
return
} }
// Offset the buffer from now on so that we can ignore ethernet frames if }
// they are present // Offset the buffer from now on so that we can ignore ethernet frames if
bs := recvd[offset:] // they are present
// If we detect an ICMP packet then hand it to the ICMPv6 module bs := recvd[offset:]
if bs[6] == 58 { // If we detect an ICMP packet then hand it to the ICMPv6 module
// Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full if bs[6] == 58 {
// Ethernet frame rather than just the IPv6 packet as this is needed for // Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full
// NDP to work correctly // Ethernet frame rather than just the IPv6 packet as this is needed for
if err := tun.icmpv6.ParsePacket(recvd); err == nil { // NDP to work correctly
// We acted on the packet in the ICMPv6 module so don't forward or do if err := tun.icmpv6.ParsePacket(recvd); err == nil {
// anything else with it // We acted on the packet in the ICMPv6 module so don't forward or do
continue // anything else with it
} return
} }
if offset != 0 { }
// Shift forward to avoid leaking bytes off the front of the slice when we eventually store it if offset != 0 {
bs = append(recvd[:0], bs...) // Shift forward to avoid leaking bytes off the front of the slice when we eventually store it
bs = append(recvd[:0], bs...)
}
// From the IP header, work out what our source and destination addresses
// and node IDs are. We will need these in order to work out where to send
// the packet
var dstAddr address.Address
var dstSnet address.Subnet
var addrlen int
n := len(bs)
// Check the IP protocol - if it doesn't match then we drop the packet and
// do nothing with it
if bs[0]&0xf0 == 0x60 {
// Check if we have a fully-sized IPv6 header
if len(bs) < 40 {
return
} }
// From the IP header, work out what our source and destination addresses // Check the packet size
// and node IDs are. We will need these in order to work out where to send if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) {
// the packet return
var dstAddr address.Address
var dstSnet address.Subnet
var addrlen int
n := len(bs)
// Check the IP protocol - if it doesn't match then we drop the packet and
// do nothing with it
if bs[0]&0xf0 == 0x60 {
// Check if we have a fully-sized IPv6 header
if len(bs) < 40 {
continue
}
// Check the packet size
if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) {
continue
}
// IPv6 address
addrlen = 16
copy(dstAddr[:addrlen], bs[24:])
copy(dstSnet[:addrlen/2], bs[24:])
} else if bs[0]&0xf0 == 0x40 {
// Check if we have a fully-sized IPv4 header
if len(bs) < 20 {
continue
}
// Check the packet size
if n != 256*int(bs[2])+int(bs[3]) {
continue
}
// IPv4 address
addrlen = 4
copy(dstAddr[:addrlen], bs[16:])
} else {
// Unknown address length or protocol, so drop the packet and ignore it
tun.log.Traceln("Unknown packet type, dropping")
continue
} }
if tun.ckr.isEnabled() { // IPv6 address
if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { addrlen = 16
if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { copy(dstAddr[:addrlen], bs[24:])
// A public key was found, get the node ID for the search copy(dstSnet[:addrlen/2], bs[24:])
dstNodeID := crypto.GetNodeID(&key) } else if bs[0]&0xf0 == 0x40 {
dstAddr = *address.AddrForNodeID(dstNodeID) // Check if we have a fully-sized IPv4 header
dstSnet = *address.SubnetForNodeID(dstNodeID) if len(bs) < 20 {
addrlen = 16 return
}
}
} }
// Check the packet size
if n != 256*int(bs[2])+int(bs[3]) {
return
}
// IPv4 address
addrlen = 4
copy(dstAddr[:addrlen], bs[16:])
} else {
// Unknown address length or protocol, so drop the packet and ignore it
tun.log.Traceln("Unknown packet type, dropping")
return
}
if tun.ckr.isEnabled() {
if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) {
// Couldn't find this node's ygg IP if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil {
continue // A public key was found, get the node ID for the search
} dstNodeID := crypto.GetNodeID(&key)
// Do we have an active connection for this node address? dstAddr = *address.AddrForNodeID(dstNodeID)
var dstNodeID, dstNodeIDMask *crypto.NodeID dstSnet = *address.SubnetForNodeID(dstNodeID)
tun.mutex.RLock() addrlen = 16
session, isIn := tun.addrToConn[dstAddr]
if !isIn || session == nil {
session, isIn = tun.subnetToConn[dstSnet]
if !isIn || session == nil {
// Neither an address nor a subnet mapping matched, therefore populate
// the node ID and mask to commence a search
if dstAddr.IsValid() {
dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask()
} else {
dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask()
}
} }
} }
tun.mutex.RUnlock() }
// If we don't have a connection then we should open one if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) {
// Couldn't find this node's ygg IP
return
}
// Do we have an active connection for this node address?
var dstNodeID, dstNodeIDMask *crypto.NodeID
session, isIn := tun.addrToConn[dstAddr]
if !isIn || session == nil {
session, isIn = tun.subnetToConn[dstSnet]
if !isIn || session == nil { if !isIn || session == nil {
// Check we haven't been given empty node ID, really this shouldn't ever // Neither an address nor a subnet mapping matched, therefore populate
// happen but just to be sure... // the node ID and mask to commence a search
if dstNodeID == nil || dstNodeIDMask == nil { if dstAddr.IsValid() {
panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask()
} else {
dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask()
} }
// Dial to the remote node }
}
// If we don't have a connection then we should open one
if !isIn || session == nil {
// Check we haven't been given empty node ID, really this shouldn't ever
// happen but just to be sure...
if dstNodeID == nil || dstNodeIDMask == nil {
panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen")
}
_, known := tun.dials[*dstNodeID]
tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs)
for len(tun.dials[*dstNodeID]) > 32 {
util.PutBytes(tun.dials[*dstNodeID][0])
tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:]
}
if !known {
go func() { go func() {
// FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask)
tun.mutex.Lock() tun.Act(nil, func() {
_, known := tun.dials[*dstNodeID] packets := tun.dials[*dstNodeID]
tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs) delete(tun.dials, *dstNodeID)
for len(tun.dials[*dstNodeID]) > 32 { if err != nil {
util.PutBytes(tun.dials[*dstNodeID][0]) return
tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] }
}
tun.mutex.Unlock()
if known {
return
}
var tc *tunConn
if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil {
// We've been given a connection so prepare the session wrapper // We've been given a connection so prepare the session wrapper
if tc, err = tun.wrap(conn); err != nil { var tc *tunConn
if tc, err = tun._wrap(conn); err != nil {
// Something went wrong when storing the connection, typically that // Something went wrong when storing the connection, typically that
// something already exists for this address or subnet // something already exists for this address or subnet
tun.log.Debugln("TUN/TAP iface wrap:", err) tun.log.Debugln("TUN/TAP iface wrap:", err)
return
} }
}
tun.mutex.Lock()
packets := tun.dials[*dstNodeID]
delete(tun.dials, *dstNodeID)
tun.mutex.Unlock()
if tc != nil {
for _, packet := range packets { for _, packet := range packets {
p := packet // Possibly required because of how range tc.writeFrom(nil, packet)
tc.send <- p
} }
} })
return
}() }()
// While the dial is going on we can't do much else
// continuing this iteration - skip to the next one
continue
}
// If we have a connection now, try writing to it
if isIn && session != nil {
session.send <- bs
} }
} }
} // If we have a connection now, try writing to it
if isIn && session != nil {
func (tun *TunAdapter) reader() error { session.writeFrom(tun, bs)
toWorker := make(chan []byte, 32)
defer close(toWorker)
go tun.readerPacketHandler(toWorker)
for {
// Get a slice to store the packet in
recvd := util.ResizeBytes(util.GetBytes(), 65535+tun_ETHER_HEADER_LENGTH)
// Wait for a packet to be delivered to us through the TUN/TAP adapter
n, err := tun.iface.Read(recvd)
if err != nil {
if !tun.isOpen {
return err
}
panic(err)
}
if n == 0 {
util.PutBytes(recvd)
continue
}
// Send the packet to the worker
toWorker <- recvd[:n]
} }
} }

View File

@ -13,8 +13,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"sync"
//"sync"
"github.com/Arceliar/phony"
"github.com/gologme/log" "github.com/gologme/log"
"github.com/yggdrasil-network/water" "github.com/yggdrasil-network/water"
@ -33,19 +35,21 @@ const tun_ETHER_HEADER_LENGTH = 14
// you should pass this object to the yggdrasil.SetRouterAdapter() function // you should pass this object to the yggdrasil.SetRouterAdapter() function
// before calling yggdrasil.Start(). // before calling yggdrasil.Start().
type TunAdapter struct { type TunAdapter struct {
config *config.NodeState writer tunWriter
log *log.Logger reader tunReader
reconfigure chan chan error config *config.NodeState
listener *yggdrasil.Listener log *log.Logger
dialer *yggdrasil.Dialer reconfigure chan chan error
addr address.Address listener *yggdrasil.Listener
subnet address.Subnet dialer *yggdrasil.Dialer
ckr cryptokey addr address.Address
icmpv6 ICMPv6 subnet address.Subnet
mtu int ckr cryptokey
iface *water.Interface icmpv6 ICMPv6
send chan []byte mtu int
mutex sync.RWMutex // Protects the below iface *water.Interface
phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
//mutex sync.RWMutex // Protects the below
addrToConn map[address.Address]*tunConn addrToConn map[address.Address]*tunConn
subnetToConn map[address.Subnet]*tunConn subnetToConn map[address.Subnet]*tunConn
dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes
@ -114,11 +118,21 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener
tun.addrToConn = make(map[address.Address]*tunConn) tun.addrToConn = make(map[address.Address]*tunConn)
tun.subnetToConn = make(map[address.Subnet]*tunConn) tun.subnetToConn = make(map[address.Subnet]*tunConn)
tun.dials = make(map[crypto.NodeID][][]byte) tun.dials = make(map[crypto.NodeID][][]byte)
tun.writer.tun = tun
tun.reader.tun = tun
} }
// Start the setup process for the TUN/TAP adapter. If successful, starts the // Start the setup process for the TUN/TAP adapter. If successful, starts the
// read/write goroutines to handle packets on that interface. // reader actor to handle packets on that interface.
func (tun *TunAdapter) Start() error { func (tun *TunAdapter) Start() error {
var err error
phony.Block(tun, func() {
err = tun._start()
})
return err
}
func (tun *TunAdapter) _start() error {
current := tun.config.GetCurrent() current := tun.config.GetCurrent()
if tun.config == nil || tun.listener == nil || tun.dialer == nil { if tun.config == nil || tun.listener == nil || tun.dialer == nil {
return errors.New("No configuration available to TUN/TAP") return errors.New("No configuration available to TUN/TAP")
@ -145,11 +159,8 @@ func (tun *TunAdapter) Start() error {
tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy") tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy")
return nil return nil
} }
tun.mutex.Lock()
tun.isOpen = true tun.isOpen = true
tun.send = make(chan []byte, 32) // TODO: is this a sensible value?
tun.reconfigure = make(chan chan error) tun.reconfigure = make(chan chan error)
tun.mutex.Unlock()
go func() { go func() {
for { for {
e := <-tun.reconfigure e := <-tun.reconfigure
@ -157,8 +168,7 @@ func (tun *TunAdapter) Start() error {
} }
}() }()
go tun.handler() go tun.handler()
go tun.reader() tun.reader.Act(nil, tun.reader._read) // Start the reader
go tun.writer()
tun.icmpv6.Init(tun) tun.icmpv6.Init(tun)
if iftapmode { if iftapmode {
go tun.icmpv6.Solicit(tun.addr) go tun.icmpv6.Solicit(tun.addr)
@ -170,6 +180,14 @@ func (tun *TunAdapter) Start() error {
// Start the setup process for the TUN/TAP adapter. If successful, starts the // Start the setup process for the TUN/TAP adapter. If successful, starts the
// read/write goroutines to handle packets on that interface. // read/write goroutines to handle packets on that interface.
func (tun *TunAdapter) Stop() error { func (tun *TunAdapter) Stop() error {
var err error
phony.Block(tun, func() {
err = tun._stop()
})
return err
}
func (tun *TunAdapter) _stop() error {
tun.isOpen = false tun.isOpen = false
// TODO: we have nothing that cleanly stops all the various goroutines opened // TODO: we have nothing that cleanly stops all the various goroutines opened
// by TUN/TAP, e.g. readers/writers, sessions // by TUN/TAP, e.g. readers/writers, sessions
@ -183,29 +201,11 @@ func (tun *TunAdapter) Stop() error {
func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) { func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) {
tun.log.Debugln("Reloading TUN/TAP configuration...") tun.log.Debugln("Reloading TUN/TAP configuration...")
// Replace the active configuration with the supplied one
tun.config.Replace(*config) tun.config.Replace(*config)
errors := 0 // Notify children about the configuration change
tun.Act(nil, tun.ckr.configure)
components := []chan chan error{
tun.reconfigure,
tun.ckr.reconfigure,
}
for _, component := range components {
response := make(chan error)
component <- response
if err := <-response; err != nil {
tun.log.Errorln(err)
errors++
}
}
if errors > 0 {
tun.log.Warnln(errors, "TUN/TAP module(s) reported errors during configuration reload")
} else {
tun.log.Infoln("TUN/TAP configuration reloaded successfully")
}
} }
func (tun *TunAdapter) handler() error { func (tun *TunAdapter) handler() error {
@ -216,22 +216,22 @@ func (tun *TunAdapter) handler() error {
tun.log.Errorln("TUN/TAP connection accept error:", err) tun.log.Errorln("TUN/TAP connection accept error:", err)
return err return err
} }
if _, err := tun.wrap(conn); err != nil { phony.Block(tun, func() {
// Something went wrong when storing the connection, typically that if _, err := tun._wrap(conn); err != nil {
// something already exists for this address or subnet // Something went wrong when storing the connection, typically that
tun.log.Debugln("TUN/TAP handler wrap:", err) // something already exists for this address or subnet
} tun.log.Debugln("TUN/TAP handler wrap:", err)
}
})
} }
} }
func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) {
// Prepare a session wrapper for the given connection // Prepare a session wrapper for the given connection
s := tunConn{ s := tunConn{
tun: tun, tun: tun,
conn: conn, conn: conn,
send: make(chan []byte, 32), // TODO: is this a sensible value? stop: make(chan struct{}),
stop: make(chan struct{}),
alive: make(chan struct{}, 1),
} }
c = &s c = &s
// Get the remote address and subnet of the other side // Get the remote address and subnet of the other side
@ -239,27 +239,28 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) {
s.addr = *address.AddrForNodeID(&remoteNodeID) s.addr = *address.AddrForNodeID(&remoteNodeID)
s.snet = *address.SubnetForNodeID(&remoteNodeID) s.snet = *address.SubnetForNodeID(&remoteNodeID)
// Work out if this is already a destination we already know about // Work out if this is already a destination we already know about
tun.mutex.Lock()
defer tun.mutex.Unlock()
atc, aok := tun.addrToConn[s.addr] atc, aok := tun.addrToConn[s.addr]
stc, sok := tun.subnetToConn[s.snet] stc, sok := tun.subnetToConn[s.snet]
// If we know about a connection for this destination already then assume it // If we know about a connection for this destination already then assume it
// is no longer valid and close it // is no longer valid and close it
if aok { if aok {
atc._close_nomutex() atc._close_from_tun()
err = errors.New("replaced connection for address") err = errors.New("replaced connection for address")
} else if sok { } else if sok {
stc._close_nomutex() stc._close_from_tun()
err = errors.New("replaced connection for subnet") err = errors.New("replaced connection for subnet")
} }
// Save the session wrapper so that we can look it up quickly next time // Save the session wrapper so that we can look it up quickly next time
// we receive a packet through the interface for this address // we receive a packet through the interface for this address
tun.addrToConn[s.addr] = &s tun.addrToConn[s.addr] = &s
tun.subnetToConn[s.snet] = &s tun.subnetToConn[s.snet] = &s
// Start the connection goroutines // Set the read callback and start the timeout
go s.reader() conn.SetReadCallback(func(bs []byte) {
go s.writer() s.Act(conn, func() {
go s.checkForTimeouts() s._read(bs)
})
})
s.Act(nil, s.stillAlive)
// Return // Return
return c, err return c, err
} }

View File

@ -6,12 +6,13 @@ import (
"fmt" "fmt"
"net" "net"
"sort" "sort"
"sync/atomic"
"time" "time"
"github.com/gologme/log" "github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/address" "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/Arceliar/phony"
) )
// Peer represents a single peer object. This contains information from the // Peer represents a single peer object. This contains information from the
@ -106,15 +107,18 @@ func (c *Core) GetPeers() []Peer {
sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] }) sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] })
for _, port := range ps { for _, port := range ps {
p := ports[port] p := ports[port]
info := Peer{ var info Peer
Endpoint: p.intf.name, phony.Block(p, func() {
BytesSent: atomic.LoadUint64(&p.bytesSent), info = Peer{
BytesRecvd: atomic.LoadUint64(&p.bytesRecvd), Endpoint: p.intf.name,
Protocol: p.intf.info.linkType, BytesSent: p.bytesSent,
Port: uint64(port), BytesRecvd: p.bytesRecvd,
Uptime: time.Since(p.firstSeen), Protocol: p.intf.info.linkType,
} Port: uint64(port),
copy(info.PublicKey[:], p.box[:]) Uptime: time.Since(p.firstSeen),
}
copy(info.PublicKey[:], p.box[:])
})
peers = append(peers, info) peers = append(peers, info)
} }
return peers return peers
@ -135,15 +139,18 @@ func (c *Core) GetSwitchPeers() []SwitchPeer {
continue continue
} }
coords := elem.locator.getCoords() coords := elem.locator.getCoords()
info := SwitchPeer{ var info SwitchPeer
Coords: append([]uint64{}, wire_coordsBytestoUint64s(coords)...), phony.Block(peer, func() {
BytesSent: atomic.LoadUint64(&peer.bytesSent), info = SwitchPeer{
BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd), Coords: append([]uint64{}, wire_coordsBytestoUint64s(coords)...),
Port: uint64(elem.port), BytesSent: peer.bytesSent,
Protocol: peer.intf.info.linkType, BytesRecvd: peer.bytesRecvd,
Endpoint: peer.intf.info.remote, Port: uint64(elem.port),
} Protocol: peer.intf.info.linkType,
copy(info.PublicKey[:], peer.box[:]) Endpoint: peer.intf.info.remote,
}
copy(info.PublicKey[:], peer.box[:])
})
switchpeers = append(switchpeers, info) switchpeers = append(switchpeers, info)
} }
return switchpeers return switchpeers
@ -156,11 +163,11 @@ func (c *Core) GetDHT() []DHTEntry {
getDHT := func() { getDHT := func() {
now := time.Now() now := time.Now()
var dhtentry []*dhtInfo var dhtentry []*dhtInfo
for _, v := range c.dht.table { for _, v := range c.router.dht.table {
dhtentry = append(dhtentry, v) dhtentry = append(dhtentry, v)
} }
sort.SliceStable(dhtentry, func(i, j int) bool { sort.SliceStable(dhtentry, func(i, j int) bool {
return dht_ordered(&c.dht.nodeID, dhtentry[i].getNodeID(), dhtentry[j].getNodeID()) return dht_ordered(&c.router.dht.nodeID, dhtentry[i].getNodeID(), dhtentry[j].getNodeID())
}) })
for _, v := range dhtentry { for _, v := range dhtentry {
info := DHTEntry{ info := DHTEntry{
@ -171,7 +178,7 @@ func (c *Core) GetDHT() []DHTEntry {
dhtentries = append(dhtentries, info) dhtentries = append(dhtentries, info)
} }
} }
c.router.doAdmin(getDHT) phony.Block(&c.router, getDHT)
return dhtentries return dhtentries
} }
@ -186,7 +193,7 @@ func (c *Core) GetSwitchQueues() SwitchQueues {
Size: switchTable.queues.size, Size: switchTable.queues.size,
HighestCount: uint64(switchTable.queues.maxbufs), HighestCount: uint64(switchTable.queues.maxbufs),
HighestSize: switchTable.queues.maxsize, HighestSize: switchTable.queues.maxsize,
MaximumSize: switchTable.queueTotalMaxSize, MaximumSize: switchTable.queues.totalMaxSize,
} }
for k, v := range switchTable.queues.bufs { for k, v := range switchTable.queues.bufs {
nexthop := switchTable.bestPortForCoords([]byte(k)) nexthop := switchTable.bestPortForCoords([]byte(k))
@ -198,9 +205,8 @@ func (c *Core) GetSwitchQueues() SwitchQueues {
} }
switchqueues.Queues = append(switchqueues.Queues, queue) switchqueues.Queues = append(switchqueues.Queues, queue)
} }
} }
c.switchTable.doAdmin(getSwitchQueues) phony.Block(&c.switchTable, getSwitchQueues)
return switchqueues return switchqueues
} }
@ -208,12 +214,12 @@ func (c *Core) GetSwitchQueues() SwitchQueues {
func (c *Core) GetSessions() []Session { func (c *Core) GetSessions() []Session {
var sessions []Session var sessions []Session
getSessions := func() { getSessions := func() {
for _, sinfo := range c.sessions.sinfos { for _, sinfo := range c.router.sessions.sinfos {
var session Session var session Session
workerFunc := func() { workerFunc := func() {
session = Session{ session = Session{
Coords: append([]uint64{}, wire_coordsBytestoUint64s(sinfo.coords)...), Coords: append([]uint64{}, wire_coordsBytestoUint64s(sinfo.coords)...),
MTU: sinfo.getMTU(), MTU: sinfo._getMTU(),
BytesSent: sinfo.bytesSent, BytesSent: sinfo.bytesSent,
BytesRecvd: sinfo.bytesRecvd, BytesRecvd: sinfo.bytesRecvd,
Uptime: time.Now().Sub(sinfo.timeOpened), Uptime: time.Now().Sub(sinfo.timeOpened),
@ -221,39 +227,28 @@ func (c *Core) GetSessions() []Session {
} }
copy(session.PublicKey[:], sinfo.theirPermPub[:]) copy(session.PublicKey[:], sinfo.theirPermPub[:])
} }
var skip bool phony.Block(sinfo, workerFunc)
func() {
defer func() {
if recover() != nil {
skip = true
}
}()
sinfo.doFunc(workerFunc)
}()
if skip {
continue
}
// TODO? skipped known but timed out sessions? // TODO? skipped known but timed out sessions?
sessions = append(sessions, session) sessions = append(sessions, session)
} }
} }
c.router.doAdmin(getSessions) phony.Block(&c.router, getSessions)
return sessions return sessions
} }
// ConnListen returns a listener for Yggdrasil session connections. // ConnListen returns a listener for Yggdrasil session connections.
func (c *Core) ConnListen() (*Listener, error) { func (c *Core) ConnListen() (*Listener, error) {
c.sessions.listenerMutex.Lock() c.router.sessions.listenerMutex.Lock()
defer c.sessions.listenerMutex.Unlock() defer c.router.sessions.listenerMutex.Unlock()
if c.sessions.listener != nil { if c.router.sessions.listener != nil {
return nil, errors.New("a listener already exists") return nil, errors.New("a listener already exists")
} }
c.sessions.listener = &Listener{ c.router.sessions.listener = &Listener{
core: c, core: c,
conn: make(chan *Conn), conn: make(chan *Conn),
close: make(chan interface{}), close: make(chan interface{}),
} }
return c.sessions.listener, nil return c.router.sessions.listener, nil
} }
// ConnDialer returns a dialer for Yggdrasil session connections. // ConnDialer returns a dialer for Yggdrasil session connections.
@ -338,11 +333,9 @@ func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool)
}) })
c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false) c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false)
} }
c.router.doAdmin(sendNodeInfoRequest) phony.Block(&c.router, sendNodeInfoRequest)
go func() { timer := time.AfterFunc(6*time.Second, func() { close(response) })
time.Sleep(6 * time.Second) defer timer.Stop()
close(response)
}()
for res := range response { for res := range response {
return *res, nil return *res, nil
} }
@ -356,10 +349,10 @@ func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool)
// received an incoming session request. The function should return true to // received an incoming session request. The function should return true to
// allow the session or false to reject it. // allow the session or false to reject it.
func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) { func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) {
c.sessions.isAllowedMutex.Lock() c.router.sessions.isAllowedMutex.Lock()
defer c.sessions.isAllowedMutex.Unlock() defer c.router.sessions.isAllowedMutex.Unlock()
c.sessions.isAllowedHandler = f c.router.sessions.isAllowedHandler = f
} }
// SetLogger sets the output logger of the Yggdrasil node after startup. This // SetLogger sets the output logger of the Yggdrasil node after startup. This
@ -445,12 +438,12 @@ func (c *Core) DHTPing(key crypto.BoxPubKey, coords []uint64, target *crypto.Nod
} }
rq := dhtReqKey{info.key, *target} rq := dhtReqKey{info.key, *target}
sendPing := func() { sendPing := func() {
c.dht.addCallback(&rq, func(res *dhtRes) { c.router.dht.addCallback(&rq, func(res *dhtRes) {
resCh <- res resCh <- res
}) })
c.dht.ping(&info, &rq.dest) c.router.dht.ping(&info, &rq.dest)
} }
c.router.doAdmin(sendPing) phony.Block(&c.router, sendPing)
// TODO: do something better than the below... // TODO: do something better than the below...
res := <-resCh res := <-resCh
if res != nil { if res != nil {

View File

@ -3,12 +3,12 @@ package yggdrasil
import ( import (
"errors" "errors"
"fmt" "fmt"
"sync"
"sync/atomic"
"time" "time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/Arceliar/phony"
) )
// ConnError implements the net.Error interface // ConnError implements the net.Error interface
@ -54,37 +54,47 @@ func (e *ConnError) Closed() bool {
} }
type Conn struct { type Conn struct {
phony.Inbox
core *Core core *Core
readDeadline atomic.Value // time.Time // TODO timer readDeadline *time.Time
writeDeadline atomic.Value // time.Time // TODO timer writeDeadline *time.Time
mutex sync.RWMutex // protects the below
nodeID *crypto.NodeID nodeID *crypto.NodeID
nodeMask *crypto.NodeID nodeMask *crypto.NodeID
session *sessionInfo session *sessionInfo
mtu uint16
readCallback func([]byte)
readBuffer chan []byte
} }
// TODO func NewConn() that initializes additional fields as needed // TODO func NewConn() that initializes additional fields as needed
func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn { func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn {
conn := Conn{ conn := Conn{
core: core, core: core,
nodeID: nodeID, nodeID: nodeID,
nodeMask: nodeMask, nodeMask: nodeMask,
session: session, session: session,
readBuffer: make(chan []byte, 1024),
} }
return &conn return &conn
} }
func (c *Conn) String() string { func (c *Conn) String() string {
c.mutex.RLock() var s string
defer c.mutex.RUnlock() phony.Block(c, func() { s = fmt.Sprintf("conn=%p", c) })
return fmt.Sprintf("conn=%p", c) return s
}
func (c *Conn) setMTU(from phony.Actor, mtu uint16) {
c.Act(from, func() { c.mtu = mtu })
} }
// This should never be called from the router goroutine, used in the dial functions // This should never be called from the router goroutine, used in the dial functions
func (c *Conn) search() error { func (c *Conn) search() error {
var sinfo *searchInfo var sinfo *searchInfo
var isIn bool var isIn bool
c.core.router.doAdmin(func() { sinfo, isIn = c.core.searches.searches[*c.nodeID] }) phony.Block(&c.core.router, func() {
sinfo, isIn = c.core.router.searches.searches[*c.nodeID]
})
if !isIn { if !isIn {
done := make(chan struct{}, 1) done := make(chan struct{}, 1)
var sess *sessionInfo var sess *sessionInfo
@ -98,8 +108,8 @@ func (c *Conn) search() error {
default: default:
} }
} }
c.core.router.doAdmin(func() { phony.Block(&c.core.router, func() {
sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted)
sinfo.continueSearch() sinfo.continueSearch()
}) })
<-done <-done
@ -112,6 +122,7 @@ func (c *Conn) search() error {
for i := range c.nodeMask { for i := range c.nodeMask {
c.nodeMask[i] = 0xFF c.nodeMask[i] = 0xFF
} }
c.session.conn = c
} }
return err return err
} else { } else {
@ -120,27 +131,27 @@ func (c *Conn) search() error {
return nil return nil
} }
// Used in session keep-alive traffic in Conn.Write // Used in session keep-alive traffic
func (c *Conn) doSearch() { func (c *Conn) doSearch() {
routerWork := func() { routerWork := func() {
// Check to see if there is a search already matching the destination // Check to see if there is a search already matching the destination
sinfo, isIn := c.core.searches.searches[*c.nodeID] sinfo, isIn := c.core.router.searches.searches[*c.nodeID]
if !isIn { if !isIn {
// Nothing was found, so create a new search // Nothing was found, so create a new search
searchCompleted := func(sinfo *sessionInfo, e error) {} searchCompleted := func(sinfo *sessionInfo, e error) {}
sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted)
c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo)
// Start the search // Start the search
sinfo.continueSearch() sinfo.continueSearch()
} }
} }
go func() { c.core.router.admin <- routerWork }() c.core.router.Act(c.session, routerWork)
} }
func (c *Conn) getDeadlineCancellation(value *atomic.Value) (util.Cancellation, bool) { func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) {
if deadline, ok := value.Load().(time.Time); ok { if t != nil {
// A deadline is set, so return a Cancellation that uses it // A deadline is set, so return a Cancellation that uses it
c := util.CancellationWithDeadline(c.session.cancel, deadline) c := util.CancellationWithDeadline(c.session.cancel, *t)
return c, true return c, true
} else { } else {
// No deadline was set, so just return the existinc cancellation and a dummy value // No deadline was set, so just return the existinc cancellation and a dummy value
@ -148,9 +159,45 @@ func (c *Conn) getDeadlineCancellation(value *atomic.Value) (util.Cancellation,
} }
} }
// SetReadCallback sets a callback which will be called whenever a packet is received.
func (c *Conn) SetReadCallback(callback func([]byte)) {
c.Act(nil, func() {
c.readCallback = callback
c._drainReadBuffer()
})
}
func (c *Conn) _drainReadBuffer() {
if c.readCallback == nil {
return
}
select {
case bs := <-c.readBuffer:
c.readCallback(bs)
c.Act(nil, c._drainReadBuffer) // In case there's more
default:
}
}
// Called by the session to pass a new message to the Conn
func (c *Conn) recvMsg(from phony.Actor, msg []byte) {
c.Act(from, func() {
if c.readCallback != nil {
c.readCallback(msg)
} else {
select {
case c.readBuffer <- msg:
default:
}
}
})
}
// Used internally by Read, the caller is responsible for util.PutBytes when they're done. // Used internally by Read, the caller is responsible for util.PutBytes when they're done.
func (c *Conn) ReadNoCopy() ([]byte, error) { func (c *Conn) ReadNoCopy() ([]byte, error) {
cancel, doCancel := c.getDeadlineCancellation(&c.readDeadline) var cancel util.Cancellation
var doCancel bool
phony.Block(c, func() { cancel, doCancel = c._getDeadlineCancellation(c.readDeadline) })
if doCancel { if doCancel {
defer cancel.Cancel(nil) defer cancel.Cancel(nil)
} }
@ -162,7 +209,7 @@ func (c *Conn) ReadNoCopy() ([]byte, error) {
} else { } else {
return nil, ConnError{errors.New("session closed"), false, false, true, 0} return nil, ConnError{errors.New("session closed"), false, false, true, 0}
} }
case bs := <-c.session.recv: case bs := <-c.readBuffer:
return bs, nil return bs, nil
} }
} }
@ -185,49 +232,63 @@ func (c *Conn) Read(b []byte) (int, error) {
return n, err return n, err
} }
// Used internally by Write, the caller must not reuse the argument bytes when no error occurs func (c *Conn) _write(msg FlowKeyMessage) error {
func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { if len(msg.Message) > int(c.mtu) {
var err error return ConnError{errors.New("packet too big"), true, false, false, int(c.mtu)}
sessionFunc := func() { }
// Does the packet exceed the permitted size for the session? c.session.Act(c, func() {
if uint16(len(msg.Message)) > c.session.getMTU() { // Send the packet
err = ConnError{errors.New("packet too big"), true, false, false, int(c.session.getMTU())} c.session._send(msg)
return // Session keep-alive, while we wait for the crypto workers from send
}
// The rest of this work is session keep-alive traffic
switch { switch {
case time.Since(c.session.time) > 6*time.Second: case time.Since(c.session.time) > 6*time.Second:
if c.session.time.Before(c.session.pingTime) && time.Since(c.session.pingTime) > 6*time.Second { if c.session.time.Before(c.session.pingTime) && time.Since(c.session.pingTime) > 6*time.Second {
// TODO double check that the above condition is correct // TODO double check that the above condition is correct
c.doSearch() c.doSearch()
} else { } else {
c.core.sessions.ping(c.session) c.session.ping(c.session) // TODO send from self if this becomes an actor
} }
case c.session.reset && c.session.pingTime.Before(c.session.time): case c.session.reset && c.session.pingTime.Before(c.session.time):
c.core.sessions.ping(c.session) c.session.ping(c.session) // TODO send from self if this becomes an actor
default: // Don't do anything, to keep traffic throttled default: // Don't do anything, to keep traffic throttled
} }
} })
c.session.doFunc(sessionFunc) return nil
if err == nil { }
cancel, doCancel := c.getDeadlineCancellation(&c.writeDeadline)
if doCancel { // WriteFrom should be called by a phony.Actor, and tells the Conn to send a message.
defer cancel.Cancel(nil) // This is used internaly by WriteNoCopy and Write.
} // If the callback is called with a non-nil value, then it is safe to reuse the argument FlowKeyMessage.
select { func (c *Conn) WriteFrom(from phony.Actor, msg FlowKeyMessage, callback func(error)) {
case <-cancel.Finished(): c.Act(from, func() {
if cancel.Error() == util.CancellationTimeoutError { callback(c._write(msg))
err = ConnError{errors.New("write timeout"), true, false, false, 0} })
} else { }
err = ConnError{errors.New("session closed"), false, false, true, 0}
} // WriteNoCopy is used internally by Write and makes use of WriteFrom under the hood.
case c.session.send <- msg: // The caller must not reuse the argument FlowKeyMessage when a nil error is returned.
func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error {
var cancel util.Cancellation
var doCancel bool
phony.Block(c, func() { cancel, doCancel = c._getDeadlineCancellation(c.writeDeadline) })
var err error
select {
case <-cancel.Finished():
if cancel.Error() == util.CancellationTimeoutError {
err = ConnError{errors.New("write timeout"), true, false, false, 0}
} else {
err = ConnError{errors.New("session closed"), false, false, true, 0}
} }
default:
done := make(chan struct{})
callback := func(e error) { err = e; close(done) }
c.WriteFrom(nil, msg, callback)
<-done
} }
return err return err
} }
// Implements net.Conn.Write // Write implement the Write function of a net.Conn, and makes use of WriteNoCopy under the hood.
func (c *Conn) Write(b []byte) (int, error) { func (c *Conn) Write(b []byte) (int, error) {
written := len(b) written := len(b)
msg := FlowKeyMessage{Message: append(util.GetBytes(), b...)} msg := FlowKeyMessage{Message: append(util.GetBytes(), b...)}
@ -240,25 +301,28 @@ func (c *Conn) Write(b []byte) (int, error) {
} }
func (c *Conn) Close() (err error) { func (c *Conn) Close() (err error) {
c.mutex.Lock() phony.Block(c, func() {
defer c.mutex.Unlock() if c.session != nil {
if c.session != nil { // Close the session, if it hasn't been closed already
// Close the session, if it hasn't been closed already if e := c.session.cancel.Cancel(errors.New("connection closed")); e != nil {
if e := c.session.cancel.Cancel(errors.New("connection closed")); e != nil { err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0}
err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0} } else {
c.session.doRemove()
}
} }
} })
return return
} }
func (c *Conn) LocalAddr() crypto.NodeID { func (c *Conn) LocalAddr() crypto.NodeID {
return *crypto.GetNodeID(&c.session.core.boxPub) return *crypto.GetNodeID(&c.core.boxPub)
} }
func (c *Conn) RemoteAddr() crypto.NodeID { func (c *Conn) RemoteAddr() crypto.NodeID {
c.mutex.RLock() // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors...
defer c.mutex.RUnlock() var n crypto.NodeID
return *c.nodeID phony.Block(c, func() { n = *c.nodeID })
return n
} }
func (c *Conn) SetDeadline(t time.Time) error { func (c *Conn) SetDeadline(t time.Time) error {
@ -268,11 +332,13 @@ func (c *Conn) SetDeadline(t time.Time) error {
} }
func (c *Conn) SetReadDeadline(t time.Time) error { func (c *Conn) SetReadDeadline(t time.Time) error {
c.readDeadline.Store(t) // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors...
phony.Block(c, func() { c.readDeadline = &t })
return nil return nil
} }
func (c *Conn) SetWriteDeadline(t time.Time) error { func (c *Conn) SetWriteDeadline(t time.Time) error {
c.writeDeadline.Store(t) // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors...
phony.Block(c, func() { c.writeDeadline = &t })
return nil return nil
} }

View File

@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"time" "time"
"github.com/Arceliar/phony"
"github.com/gologme/log" "github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/config"
@ -19,6 +20,7 @@ type Core struct {
// This is the main data structure that holds everything else for a node // This is the main data structure that holds everything else for a node
// We're going to keep our own copy of the provided config - that way we can // We're going to keep our own copy of the provided config - that way we can
// guarantee that it will be covered by the mutex // guarantee that it will be covered by the mutex
phony.Inbox
config config.NodeState // Config config config.NodeState // Config
boxPub crypto.BoxPubKey boxPub crypto.BoxPubKey
boxPriv crypto.BoxPrivKey boxPriv crypto.BoxPrivKey
@ -26,15 +28,12 @@ type Core struct {
sigPriv crypto.SigPrivKey sigPriv crypto.SigPrivKey
switchTable switchTable switchTable switchTable
peers peers peers peers
sessions sessions
router router router router
dht dht
searches searches
link link link link
log *log.Logger log *log.Logger
} }
func (c *Core) init() error { func (c *Core) _init() error {
// TODO separate init and start functions // TODO separate init and start functions
// Init sets up structs // Init sets up structs
// Start launches goroutines that depend on structs being set up // Start launches goroutines that depend on structs being set up
@ -76,9 +75,6 @@ func (c *Core) init() error {
c.log.Warnln("SigningPublicKey in config is incorrect, should be", sp) c.log.Warnln("SigningPublicKey in config is incorrect, should be", sp)
} }
c.searches.init(c)
c.dht.init(c)
c.sessions.init(c)
c.peers.init(c) c.peers.init(c)
c.router.init(c) c.router.init(c)
c.switchTable.init(c) // TODO move before peers? before router? c.switchTable.init(c) // TODO move before peers? before router?
@ -89,64 +85,44 @@ func (c *Core) init() error {
// If any static peers were provided in the configuration above then we should // If any static peers were provided in the configuration above then we should
// configure them. The loop ensures that disconnected peers will eventually // configure them. The loop ensures that disconnected peers will eventually
// be reconnected with. // be reconnected with.
func (c *Core) addPeerLoop() { func (c *Core) _addPeerLoop() {
for { // Get the peers from the config - these could change!
// the peers from the config - these could change! current := c.config.GetCurrent()
current := c.config.GetCurrent()
// Add peers from the Peers section // Add peers from the Peers section
for _, peer := range current.Peers { for _, peer := range current.Peers {
go c.AddPeer(peer, "") go c.AddPeer(peer, "") // TODO: this should be acted and not in a goroutine?
time.Sleep(time.Second)
}
// Add peers from the InterfacePeers section
for intf, intfpeers := range current.InterfacePeers {
for _, peer := range intfpeers {
go c.AddPeer(peer, intf) // TODO: this should be acted and not in a goroutine?
time.Sleep(time.Second) time.Sleep(time.Second)
} }
// Add peers from the InterfacePeers section
for intf, intfpeers := range current.InterfacePeers {
for _, peer := range intfpeers {
go c.AddPeer(peer, intf)
time.Sleep(time.Second)
}
}
// Sit for a while
time.Sleep(time.Minute)
} }
// Sit for a while
time.AfterFunc(time.Minute, func() {
c.Act(c, c._addPeerLoop)
})
} }
// UpdateConfig updates the configuration in Core with the provided // UpdateConfig updates the configuration in Core with the provided
// config.NodeConfig and then signals the various module goroutines to // config.NodeConfig and then signals the various module goroutines to
// reconfigure themselves if needed. // reconfigure themselves if needed.
func (c *Core) UpdateConfig(config *config.NodeConfig) { func (c *Core) UpdateConfig(config *config.NodeConfig) {
c.log.Debugln("Reloading node configuration...") c.Act(nil, func() {
c.log.Debugln("Reloading node configuration...")
c.config.Replace(*config) // Replace the active configuration with the supplied one
c.config.Replace(*config)
errors := 0 // Notify the router and switch about the new configuration
c.router.Act(c, c.router.reconfigure)
components := []chan chan error{ c.switchTable.Act(c, c.switchTable.reconfigure)
c.searches.reconfigure, })
c.dht.reconfigure,
c.sessions.reconfigure,
c.peers.reconfigure,
c.router.reconfigure,
c.switchTable.reconfigure,
c.link.reconfigure,
}
for _, component := range components {
response := make(chan error)
component <- response
if err := <-response; err != nil {
c.log.Errorln(err)
errors++
}
}
if errors > 0 {
c.log.Warnln(errors, "node module(s) reported errors during configuration reload")
} else {
c.log.Infoln("Node configuration reloaded successfully")
}
} }
// Start starts up Yggdrasil using the provided config.NodeConfig, and outputs // Start starts up Yggdrasil using the provided config.NodeConfig, and outputs
@ -154,7 +130,15 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) {
// TCP and UDP sockets, a multicast discovery socket, an admin socket, router, // TCP and UDP sockets, a multicast discovery socket, an admin socket, router,
// switch and DHT node. A config.NodeState is returned which contains both the // switch and DHT node. A config.NodeState is returned which contains both the
// current and previous configurations (from reconfigures). // current and previous configurations (from reconfigures).
func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, error) { func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (conf *config.NodeState, err error) {
phony.Block(c, func() {
conf, err = c._start(nc, log)
})
return
}
// This function is unsafe and should only be ran by the core actor.
func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, error) {
c.log = log c.log = log
c.config = config.NodeState{ c.config = config.NodeState{
@ -170,20 +154,13 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState,
} }
c.log.Infoln("Starting up...") c.log.Infoln("Starting up...")
c._init()
c.init()
if err := c.link.init(c); err != nil { if err := c.link.init(c); err != nil {
c.log.Errorln("Failed to start link interfaces") c.log.Errorln("Failed to start link interfaces")
return nil, err return nil, err
} }
c.config.Mutex.RLock()
if c.config.Current.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize {
c.switchTable.queueTotalMaxSize = c.config.Current.SwitchOptions.MaxTotalQueueSize
}
c.config.Mutex.RUnlock()
if err := c.switchTable.start(); err != nil { if err := c.switchTable.start(); err != nil {
c.log.Errorln("Failed to start switch") c.log.Errorln("Failed to start switch")
return nil, err return nil, err
@ -194,7 +171,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState,
return nil, err return nil, err
} }
go c.addPeerLoop() c.Act(c, c._addPeerLoop)
c.log.Infoln("Startup complete") c.log.Infoln("Startup complete")
return &c.config, nil return &c.config, nil
@ -202,5 +179,10 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState,
// Stop shuts down the Yggdrasil node. // Stop shuts down the Yggdrasil node.
func (c *Core) Stop() { func (c *Core) Stop() {
phony.Block(c, c._stop)
}
// This function is unsafe and should only be ran by the core actor.
func (c *Core) _stop() {
c.log.Infoln("Stopping...") c.log.Infoln("Stopping...")
} }

View File

@ -2,20 +2,7 @@
package yggdrasil package yggdrasil
// These are functions that should not exist
// They are (or were) used during development, to work around missing features
// They're also used to configure things from the outside
// It would be better to define and export a few config functions elsewhere
// Or define some remote API and call it to send/request configuration info
import _ "golang.org/x/net/ipv6" // TODO put this somewhere better
//import "golang.org/x/net/proxy"
import "fmt" import "fmt"
import "net"
import "regexp"
import "encoding/hex"
import _ "net/http/pprof" import _ "net/http/pprof"
import "net/http" import "net/http"
@ -24,11 +11,6 @@ import "os"
import "github.com/gologme/log" import "github.com/gologme/log"
import "github.com/yggdrasil-network/yggdrasil-go/src/address"
import "github.com/yggdrasil-network/yggdrasil-go/src/config"
import "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
import "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
// Start the profiler in debug builds, if the required environment variable is set. // Start the profiler in debug builds, if the required environment variable is set.
func init() { func init() {
envVarName := "PPROFLISTEN" envVarName := "PPROFLISTEN"
@ -49,580 +31,3 @@ func StartProfiler(log *log.Logger) error {
go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }()
return nil return nil
} }
// This function is only called by the simulator to set up a node with random
// keys. It should not be used and may be removed in the future.
func (c *Core) Init() {
bpub, bpriv := crypto.NewBoxKeys()
spub, spriv := crypto.NewSigKeys()
hbpub := hex.EncodeToString(bpub[:])
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
cfg := config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
SigningPrivateKey: hspriv,
}
c.config = config.NodeState{
Current: cfg,
Previous: cfg,
}
c.init()
c.switchTable.start()
c.router.start()
}
////////////////////////////////////////////////////////////////////////////////
// Core
func (c *Core) DEBUG_getSigningPublicKey() crypto.SigPubKey {
return (crypto.SigPubKey)(c.sigPub)
}
func (c *Core) DEBUG_getEncryptionPublicKey() crypto.BoxPubKey {
return (crypto.BoxPubKey)(c.boxPub)
}
/*
func (c *Core) DEBUG_getSend() chan<- []byte {
return c.router.tun.send
}
func (c *Core) DEBUG_getRecv() <-chan []byte {
return c.router.tun.recv
}
*/
// Peer
func (c *Core) DEBUG_getPeers() *peers {
return &c.peers
}
func (ps *peers) DEBUG_newPeer(box crypto.BoxPubKey, sig crypto.SigPubKey, link crypto.BoxSharedKey) *peer {
sim := linkInterface{
name: "(simulator)",
info: linkInfo{
local: "(simulator)",
remote: "(simulator)",
linkType: "sim",
},
}
return ps.newPeer(&box, &sig, &link, &sim, nil)
}
/*
func (ps *peers) DEBUG_startPeers() {
ps.mutex.RLock()
defer ps.mutex.RUnlock()
for _, p := range ps.ports {
if p == nil { continue }
go p.MainLoop()
}
}
*/
func (ps *peers) DEBUG_hasPeer(key crypto.SigPubKey) bool {
ports := ps.ports.Load().(map[switchPort]*peer)
for _, p := range ports {
if p == nil {
continue
}
if p.sig == key {
return true
}
}
return false
}
func (ps *peers) DEBUG_getPorts() map[switchPort]*peer {
ports := ps.ports.Load().(map[switchPort]*peer)
newPeers := make(map[switchPort]*peer)
for port, p := range ports {
newPeers[port] = p
}
return newPeers
}
func (p *peer) DEBUG_getSigKey() crypto.SigPubKey {
return p.sig
}
func (p *peer) DEEBUG_getPort() switchPort {
return p.port
}
// Router
func (c *Core) DEBUG_getSwitchTable() *switchTable {
return &c.switchTable
}
func (c *Core) DEBUG_getLocator() switchLocator {
return c.switchTable.getLocator()
}
func (l *switchLocator) DEBUG_getCoords() []byte {
return l.getCoords()
}
func (c *Core) DEBUG_switchLookup(dest []byte) switchPort {
return c.switchTable.DEBUG_lookup(dest)
}
// This does the switch layer lookups that decide how to route traffic.
// Traffic uses greedy routing in a metric space, where the metric distance between nodes is equal to the distance between them on the tree.
// Traffic must be routed to a node that is closer to the destination via the metric space distance.
// In the event that two nodes are equally close, it gets routed to the one with the longest uptime (due to the order that things are iterated over).
// The size of the outgoing packet queue is added to a node's tree distance when the cost of forwarding to a node, subject to the constraint that the real tree distance puts them closer to the destination than ourself.
// Doing so adds a limited form of backpressure routing, based on local information, which allows us to forward traffic around *local* bottlenecks, provided that another greedy path exists.
func (t *switchTable) DEBUG_lookup(dest []byte) switchPort {
table := t.getTable()
myDist := table.self.dist(dest)
if myDist == 0 {
return 0
}
// cost is in units of (expected distance) + (expected queue size), where expected distance is used as an approximation of the minimum backpressure gradient needed for packets to flow
ports := t.core.peers.getPorts()
var best switchPort
bestCost := int64(^uint64(0) >> 1)
for _, info := range table.elems {
dist := info.locator.dist(dest)
if !(dist < myDist) {
continue
}
//p, isIn := ports[info.port]
_, isIn := ports[info.port]
if !isIn {
continue
}
cost := int64(dist) // + p.getQueueSize()
if cost < bestCost {
best = info.port
bestCost = cost
}
}
return best
}
/*
func (t *switchTable) DEBUG_isDirty() bool {
//data := t.data.Load().(*tabledata)
t.mutex.RLock()
defer t.mutex.RUnlock()
data := t.data
return data.dirty
}
*/
func (t *switchTable) DEBUG_dumpTable() {
//data := t.data.Load().(*tabledata)
t.mutex.RLock()
defer t.mutex.RUnlock()
data := t.data
for _, peer := range data.peers {
//fmt.Println("DUMPTABLE:", t.treeID, peer.treeID, peer.port,
// peer.locator.Root, peer.coords,
// peer.reverse.Root, peer.reverse.Coords, peer.forward)
fmt.Println("DUMPTABLE:", t.key, peer.key, peer.locator.coords, peer.port /*, peer.forward*/)
}
}
func (t *switchTable) DEBUG_getReversePort(port switchPort) switchPort {
// Returns Port(0) if it cannot get the reverse peer for any reason
//data := t.data.Load().(*tabledata)
t.mutex.RLock()
defer t.mutex.RUnlock()
data := t.data
if port >= switchPort(len(data.peers)) {
return switchPort(0)
}
pinfo := data.peers[port]
if len(pinfo.locator.coords) < 1 {
return switchPort(0)
}
return pinfo.locator.coords[len(pinfo.locator.coords)-1]
}
// Wire
func DEBUG_wire_encode_coords(coords []byte) []byte {
return wire_encode_coords(coords)
}
// DHT, via core
func (c *Core) DEBUG_getDHTSize() int {
var total int
c.router.doAdmin(func() {
total = len(c.dht.table)
})
return total
}
// TUN defaults
func (c *Core) DEBUG_GetTUNDefaultIfName() string {
return defaults.GetDefaults().DefaultIfName
}
func (c *Core) DEBUG_GetTUNDefaultIfMTU() int {
return defaults.GetDefaults().DefaultIfMTU
}
func (c *Core) DEBUG_GetTUNDefaultIfTAPMode() bool {
return defaults.GetDefaults().DefaultIfTAPMode
}
// udpInterface
// FIXME udpInterface isn't exported
// So debug functions need to work differently...
/*
func (c *Core) DEBUG_setupLoopbackUDPInterface() {
iface := udpInterface{}
iface.init(c, "[::1]:0")
c.ifaces = append(c.ifaces[:0], &iface)
}
*/
/*
func (c *Core) DEBUG_getLoopbackAddr() net.Addr {
iface := c.ifaces[0]
return iface.sock.LocalAddr()
}
*/
/*
func (c *Core) DEBUG_addLoopbackPeer(addr *net.UDPAddr,
in (chan<- []byte),
out (<-chan []byte)) {
iface := c.ifaces[0]
iface.addPeer(addr, in, out)
}
*/
/*
func (c *Core) DEBUG_startLoopbackUDPInterface() {
iface := c.ifaces[0]
go iface.reader()
for addr, chs := range iface.peers {
udpAddr, err := net.ResolveUDPAddr("udp6", addr)
if err != nil { panic(err) }
go iface.writer(udpAddr, chs.out)
}
}
*/
////////////////////////////////////////////////////////////////////////////////
func (c *Core) DEBUG_getAddr() *address.Address {
return address.AddrForNodeID(&c.dht.nodeID)
}
/*
func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) {
c.DEBUG_startTunWithMTU(ifname, iftapmode, 1280)
}
func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) {
addr := c.DEBUG_getAddr()
straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address.GetPrefix()))
if ifname != "none" {
err := c.router.tun.setup(ifname, iftapmode, straddr, mtu)
if err != nil {
panic(err)
}
c.log.Println("Setup TUN/TAP:", c.router.tun.iface.Name(), straddr)
go func() { panic(c.router.tun.read()) }()
}
go func() { panic(c.router.tun.write()) }()
}
func (c *Core) DEBUG_stopTun() {
c.router.tun.close()
}
*/
////////////////////////////////////////////////////////////////////////////////
func (c *Core) DEBUG_newBoxKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) {
return crypto.NewBoxKeys()
}
func (c *Core) DEBUG_getSharedKey(myPrivKey *crypto.BoxPrivKey, othersPubKey *crypto.BoxPubKey) *crypto.BoxSharedKey {
return crypto.GetSharedKey(myPrivKey, othersPubKey)
}
func (c *Core) DEBUG_newSigKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) {
return crypto.NewSigKeys()
}
func (c *Core) DEBUG_getNodeID(pub *crypto.BoxPubKey) *crypto.NodeID {
return crypto.GetNodeID(pub)
}
func (c *Core) DEBUG_getTreeID(pub *crypto.SigPubKey) *crypto.TreeID {
return crypto.GetTreeID(pub)
}
func (c *Core) DEBUG_addrForNodeID(nodeID *crypto.NodeID) string {
return net.IP(address.AddrForNodeID(nodeID)[:]).String()
}
func (c *Core) DEBUG_init(bpub []byte,
bpriv []byte,
spub []byte,
spriv []byte) {
/*var boxPub crypto.BoxPubKey
var boxPriv crypto.BoxPrivKey
var sigPub crypto.SigPubKey
var sigPriv crypto.SigPrivKey
copy(boxPub[:], bpub)
copy(boxPriv[:], bpriv)
copy(sigPub[:], spub)
copy(sigPriv[:], spriv)
c.init(&boxPub, &boxPriv, &sigPub, &sigPriv)*/
hbpub := hex.EncodeToString(bpub[:])
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
cfg := config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
SigningPrivateKey: hspriv,
}
c.config = config.NodeState{
Current: cfg,
Previous: cfg,
}
c.init()
if err := c.router.start(); err != nil {
panic(err)
}
}
////////////////////////////////////////////////////////////////////////////////
/*
func (c *Core) DEBUG_setupAndStartGlobalUDPInterface(addrport string) {
if err := c.udp.init(c, addrport); err != nil {
c.log.Println("Failed to start UDP interface:", err)
panic(err)
}
}
func (c *Core) DEBUG_getGlobalUDPAddr() *net.UDPAddr {
return c.udp.sock.LocalAddr().(*net.UDPAddr)
}
func (c *Core) DEBUG_maybeSendUDPKeys(saddr string) {
udpAddr, err := net.ResolveUDPAddr("udp", saddr)
if err != nil {
panic(err)
}
var addr connAddr
addr.fromUDPAddr(udpAddr)
c.udp.mutex.RLock()
_, isIn := c.udp.conns[addr]
c.udp.mutex.RUnlock()
if !isIn {
c.udp.sendKeys(addr)
}
}
*/
////////////////////////////////////////////////////////////////////////////////
/*
func (c *Core) DEBUG_addPeer(addr string) {
err := c.admin.addPeer(addr, "")
if err != nil {
panic(err)
}
}
*/
/*
func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) {
go func() {
dialer, err := proxy.SOCKS5("tcp", socksaddr, nil, proxy.Direct)
if err == nil {
conn, err := dialer.Dial("tcp", peeraddr)
if err == nil {
c.tcp.callWithConn(&wrappedConn{
c: conn,
raddr: &wrappedAddr{
network: "tcp",
addr: peeraddr,
},
})
}
}
}()
}
*/
/*
func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) {
c.config.Listen = []string{addrport}
if err := c.link.init(c); err != nil {
c.log.Println("Failed to start interfaces:", err)
panic(err)
}
}
func (c *Core) DEBUG_getGlobalTCPAddr() *net.TCPAddr {
return c.link.tcp.getAddr()
}
func (c *Core) DEBUG_addTCPConn(saddr string) {
c.link.tcp.call(saddr, nil, "")
}
//*/
/*
func (c *Core) DEBUG_startSelfPeer() {
c.Peers.mutex.RLock()
defer c.Peers.mutex.RUnlock()
p := c.Peers.ports[0]
go p.MainLoop()
}
*/
////////////////////////////////////////////////////////////////////////////////
/*
func (c *Core) DEBUG_setupAndStartGlobalKCPInterface(addrport string) {
iface := kcpInterface{}
iface.init(c, addrport)
c.kcp = &iface
}
func (c *Core) DEBUG_getGlobalKCPAddr() net.Addr {
return c.kcp.serv.Addr()
}
func (c *Core) DEBUG_addKCPConn(saddr string) {
c.kcp.call(saddr)
}
*/
////////////////////////////////////////////////////////////////////////////////
/*
func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) {
a := admin{}
c.config.AdminListen = addrport
a.init()
c.admin = a
}
func (c *Core) DEBUG_setupAndStartMulticastInterface() {
m := multicast{}
m.init(c)
c.multicast = m
m.start()
}
*/
////////////////////////////////////////////////////////////////////////////////
func (c *Core) DEBUG_setLogger(log *log.Logger) {
c.log = log
}
func (c *Core) DEBUG_setIfceExpr(expr *regexp.Regexp) {
c.log.Println("DEBUG_setIfceExpr no longer implemented")
}
/*
func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) {
err := c.admin.addAllowedEncryptionPublicKey(boxStr)
if err != nil {
panic(err)
}
}
*/
////////////////////////////////////////////////////////////////////////////////
func DEBUG_simLinkPeers(p, q *peer) {
// Sets q.out() to point to p and starts p.linkLoop()
goWorkers := func(source, dest *peer) {
source.linkOut = make(chan []byte, 1)
send := make(chan []byte, 1)
source.out = func(bss [][]byte) {
for _, bs := range bss {
send <- bs
}
}
go source.linkLoop()
go func() {
var packets [][]byte
for {
select {
case packet := <-source.linkOut:
packets = append(packets, packet)
continue
case packet := <-send:
packets = append(packets, packet)
source.core.switchTable.idleIn <- source.port
continue
default:
}
if len(packets) > 0 {
dest.handlePacket(packets[0])
packets = packets[1:]
continue
}
select {
case packet := <-source.linkOut:
packets = append(packets, packet)
case packet := <-send:
packets = append(packets, packet)
source.core.switchTable.idleIn <- source.port
}
}
}()
}
goWorkers(p, q)
goWorkers(q, p)
p.core.switchTable.idleIn <- p.port
q.core.switchTable.idleIn <- q.port
}
/*
func (c *Core) DEBUG_simFixMTU() {
c.router.tun.mtu = 65535
}
*/
////////////////////////////////////////////////////////////////////////////////
func Util_testAddrIDMask() {
for idx := 0; idx < 16; idx++ {
var orig crypto.NodeID
orig[8] = 42
for bidx := 0; bidx < idx; bidx++ {
orig[bidx/8] |= (0x80 >> uint8(bidx%8))
}
addr := address.AddrForNodeID(&orig)
nid, mask := addr.GetNodeIDandMask()
for b := 0; b < len(mask); b++ {
nid[b] &= mask[b]
orig[b] &= mask[b]
}
if *nid != orig {
fmt.Println(orig)
fmt.Println(*addr)
fmt.Println(*nid)
fmt.Println(*mask)
panic(idx)
}
}
}

View File

@ -65,33 +65,27 @@ type dhtReqKey struct {
// The main DHT struct. // The main DHT struct.
type dht struct { type dht struct {
core *Core router *router
reconfigure chan chan error nodeID crypto.NodeID
nodeID crypto.NodeID reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests
peers chan *dhtInfo // other goroutines put incoming dht updates here callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks
reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests
callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks
// These next two could be replaced by a single linked list or similar... // These next two could be replaced by a single linked list or similar...
table map[crypto.NodeID]*dhtInfo table map[crypto.NodeID]*dhtInfo
imp []*dhtInfo imp []*dhtInfo
} }
// Initializes the DHT. // Initializes the DHT.
func (t *dht) init(c *Core) { func (t *dht) init(r *router) {
t.core = c t.router = r
t.reconfigure = make(chan chan error, 1) t.nodeID = *t.router.core.NodeID()
go func() {
for {
e := <-t.reconfigure
e <- nil
}
}()
t.nodeID = *t.core.NodeID()
t.peers = make(chan *dhtInfo, 1024)
t.callbacks = make(map[dhtReqKey][]dht_callbackInfo) t.callbacks = make(map[dhtReqKey][]dht_callbackInfo)
t.reset() t.reset()
} }
func (t *dht) reconfigure() {
// This is where reconfiguration would go, if we had anything to do
}
// Resets the DHT in response to coord changes. // Resets the DHT in response to coord changes.
// This empties all info from the DHT and drops outstanding requests. // This empties all info from the DHT and drops outstanding requests.
func (t *dht) reset() { func (t *dht) reset() {
@ -192,10 +186,10 @@ func dht_ordered(first, second, third *crypto.NodeID) bool {
// Update info about the node that sent the request. // Update info about the node that sent the request.
func (t *dht) handleReq(req *dhtReq) { func (t *dht) handleReq(req *dhtReq) {
// Send them what they asked for // Send them what they asked for
loc := t.core.switchTable.getLocator() loc := t.router.core.switchTable.getLocator()
coords := loc.getCoords() coords := loc.getCoords()
res := dhtRes{ res := dhtRes{
Key: t.core.boxPub, Key: t.router.core.boxPub,
Coords: coords, Coords: coords,
Dest: req.Dest, Dest: req.Dest,
Infos: t.lookup(&req.Dest, false), Infos: t.lookup(&req.Dest, false),
@ -223,17 +217,17 @@ func (t *dht) handleReq(req *dhtReq) {
func (t *dht) sendRes(res *dhtRes, req *dhtReq) { func (t *dht) sendRes(res *dhtRes, req *dhtReq) {
// Send a reply for a dhtReq // Send a reply for a dhtReq
bs := res.encode() bs := res.encode()
shared := t.core.sessions.getSharedKey(&t.core.boxPriv, &req.Key) shared := t.router.sessions.getSharedKey(&t.router.core.boxPriv, &req.Key)
payload, nonce := crypto.BoxSeal(shared, bs, nil) payload, nonce := crypto.BoxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{ p := wire_protoTrafficPacket{
Coords: req.Coords, Coords: req.Coords,
ToKey: req.Key, ToKey: req.Key,
FromKey: t.core.boxPub, FromKey: t.router.core.boxPub,
Nonce: *nonce, Nonce: *nonce,
Payload: payload, Payload: payload,
} }
packet := p.encode() packet := p.encode()
t.core.router.out(packet) t.router.out(packet)
} }
type dht_callbackInfo struct { type dht_callbackInfo struct {
@ -287,17 +281,17 @@ func (t *dht) handleRes(res *dhtRes) {
func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) {
// Send a dhtReq to the node in dhtInfo // Send a dhtReq to the node in dhtInfo
bs := req.encode() bs := req.encode()
shared := t.core.sessions.getSharedKey(&t.core.boxPriv, &dest.key) shared := t.router.sessions.getSharedKey(&t.router.core.boxPriv, &dest.key)
payload, nonce := crypto.BoxSeal(shared, bs, nil) payload, nonce := crypto.BoxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{ p := wire_protoTrafficPacket{
Coords: dest.coords, Coords: dest.coords,
ToKey: dest.key, ToKey: dest.key,
FromKey: t.core.boxPub, FromKey: t.router.core.boxPub,
Nonce: *nonce, Nonce: *nonce,
Payload: payload, Payload: payload,
} }
packet := p.encode() packet := p.encode()
t.core.router.out(packet) t.router.out(packet)
rq := dhtReqKey{dest.key, req.Dest} rq := dhtReqKey{dest.key, req.Dest}
t.reqs[rq] = time.Now() t.reqs[rq] = time.Now()
} }
@ -308,10 +302,10 @@ func (t *dht) ping(info *dhtInfo, target *crypto.NodeID) {
if target == nil { if target == nil {
target = &t.nodeID target = &t.nodeID
} }
loc := t.core.switchTable.getLocator() loc := t.router.core.switchTable.getLocator()
coords := loc.getCoords() coords := loc.getCoords()
req := dhtReq{ req := dhtReq{
Key: t.core.boxPub, Key: t.router.core.boxPub,
Coords: coords, Coords: coords,
Dest: *target, Dest: *target,
} }
@ -386,7 +380,7 @@ func (t *dht) getImportant() []*dhtInfo {
}) })
// Keep the ones that are no further than the closest seen so far // Keep the ones that are no further than the closest seen so far
minDist := ^uint64(0) minDist := ^uint64(0)
loc := t.core.switchTable.getLocator() loc := t.router.core.switchTable.getLocator()
important := infos[:0] important := infos[:0]
for _, info := range infos { for _, info := range infos {
dist := uint64(loc.dist(info.coords)) dist := uint64(loc.dist(info.coords))
@ -415,12 +409,12 @@ func (t *dht) getImportant() []*dhtInfo {
// Returns true if this is a node we need to keep track of for the DHT to work. // Returns true if this is a node we need to keep track of for the DHT to work.
func (t *dht) isImportant(ninfo *dhtInfo) bool { func (t *dht) isImportant(ninfo *dhtInfo) bool {
if ninfo.key == t.core.boxPub { if ninfo.key == t.router.core.boxPub {
return false return false
} }
important := t.getImportant() important := t.getImportant()
// Check if ninfo is of equal or greater importance to what we already know // Check if ninfo is of equal or greater importance to what we already know
loc := t.core.switchTable.getLocator() loc := t.router.core.switchTable.getLocator()
ndist := uint64(loc.dist(ninfo.coords)) ndist := uint64(loc.dist(ninfo.coords))
minDist := ^uint64(0) minDist := ^uint64(0)
for _, info := range important { for _, info := range important {

View File

@ -65,6 +65,7 @@ func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, er
conn.Close() conn.Close()
return nil, err return nil, err
} }
conn.session.setConn(nil, conn)
t := time.NewTimer(6 * time.Second) // TODO use a context instead t := time.NewTimer(6 * time.Second) // TODO use a context instead
defer t.Stop() defer t.Stop()
select { select {

View File

@ -16,14 +16,15 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/address" "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" "github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/Arceliar/phony"
) )
type link struct { type link struct {
core *Core core *Core
reconfigure chan chan error mutex sync.RWMutex // protects interfaces below
mutex sync.RWMutex // protects interfaces below interfaces map[linkInfo]*linkInterface
interfaces map[linkInfo]*linkInterface tcp tcp // TCP interface support
tcp tcp // TCP interface support
// TODO timeout (to remove from switch), read from config.ReadTimeout // TODO timeout (to remove from switch), read from config.ReadTimeout
} }
@ -45,21 +46,29 @@ type linkInterfaceMsgIO interface {
} }
type linkInterface struct { type linkInterface struct {
name string name string
link *link link *link
peer *peer peer *peer
msgIO linkInterfaceMsgIO msgIO linkInterfaceMsgIO
info linkInfo info linkInfo
incoming bool incoming bool
force bool force bool
closed chan struct{} closed chan struct{}
reader linkReader // Reads packets, notifies this linkInterface, passes packets to switch
writer linkWriter // Writes packets, notifies this linkInterface
phony.Inbox // Protects the below
sendTimer *time.Timer // Fires to signal that sending is blocked
keepAliveTimer *time.Timer // Fires to send keep-alive traffic
stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen
closeTimer *time.Timer // Fires when the link has been idle so long we need to close it
inSwitch bool // True if the switch is tracking this link
stalled bool // True if we haven't been receiving any response traffic
} }
func (l *link) init(c *Core) error { func (l *link) init(c *Core) error {
l.core = c l.core = c
l.mutex.Lock() l.mutex.Lock()
l.interfaces = make(map[linkInfo]*linkInterface) l.interfaces = make(map[linkInfo]*linkInterface)
l.reconfigure = make(chan chan error)
l.mutex.Unlock() l.mutex.Unlock()
if err := l.tcp.init(l); err != nil { if err := l.tcp.init(l); err != nil {
@ -67,22 +76,13 @@ func (l *link) init(c *Core) error {
return err return err
} }
go func() {
for {
e := <-l.reconfigure
tcpresponse := make(chan error)
l.tcp.reconfigure <- tcpresponse
if err := <-tcpresponse; err != nil {
e <- err
continue
}
e <- nil
}
}()
return nil return nil
} }
func (l *link) reconfigure() {
l.tcp.reconfigure()
}
func (l *link) call(uri string, sintf string) error { func (l *link) call(uri string, sintf string) error {
u, err := url.Parse(uri) u, err := url.Parse(uri)
if err != nil { if err != nil {
@ -128,6 +128,9 @@ func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote st
incoming: incoming, incoming: incoming,
force: force, force: force,
} }
intf.writer.intf = &intf
intf.reader.intf = &intf
intf.reader.err = make(chan error)
return &intf, nil return &intf, nil
} }
@ -206,213 +209,187 @@ func (intf *linkInterface) handler() error {
// More cleanup can go here // More cleanup can go here
intf.link.core.peers.removePeer(intf.peer.port) 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(msgs [][]byte) { intf.peer.out = func(msgs [][]byte) {
defer func() { recover() }() intf.writer.sendFrom(intf.peer, msgs, false)
out <- msgs }
intf.peer.linkOut = func(bs []byte) {
intf.writer.sendFrom(intf.peer, [][]byte{bs}, true)
} }
intf.peer.linkOut = make(chan []byte, 1)
themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box))
themAddrString := net.IP(themAddr[:]).String() themAddrString := net.IP(themAddr[:]).String()
themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote) themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote)
intf.link.core.log.Infof("Connected %s: %s, source %s", intf.link.core.log.Infof("Connected %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local) strings.ToUpper(intf.info.linkType), themString, intf.info.local)
// Start the link loop // Start things
go intf.peer.linkLoop() go intf.peer.start()
// Start the writer intf.reader.Act(nil, intf.reader._read)
signalReady := make(chan struct{}, 1) // Wait for the reader to finish
signalSent := make(chan bool, 1) err = <-intf.reader.err
sendAck := make(chan struct{}, 1) if err != nil {
sendBlocked := time.NewTimer(time.Second)
defer util.TimerStop(sendBlocked)
util.TimerStop(sendBlocked)
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(bss [][]byte) {
sendBlocked.Reset(time.Second)
size, _ := intf.msgIO.writeMsgs(bss)
util.TimerStop(sendBlocked)
select {
case signalSent <- size > 0:
default:
}
}
for {
// First try to send any link protocol traffic
select {
case msg := <-intf.peer.linkOut:
send([][]byte{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.Tracef("Sending (legacy) keep-alive to %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
send([][]byte{nil})
case <-sendAck:
intf.link.core.log.Tracef("Sending ack to %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
send([][]byte{nil})
case msg := <-intf.peer.linkOut:
send([][]byte{msg})
case msgs, ok := <-out:
if !ok {
return
}
send(msgs)
for _, msg := range msgs {
util.PutBytes(msg)
}
select {
case signalReady <- struct{}{}:
default:
}
//intf.link.core.log.Tracef("Sending packet to %s: %s, source %s",
// strings.ToUpper(intf.info.linkType), themString, intf.info.local)
}
}
}()
//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)
ret := make(chan error, 1) // How we signal the return value when multiple goroutines are involved
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
closeTime := 2 * switch_timeout // TODO or maybe this makes more sense for ReadTimeout?...
sendTime := time.Second
sendTimer := time.NewTimer(sendTime)
defer util.TimerStop(sendTimer)
recvTimer := time.NewTimer(recvTime)
defer util.TimerStop(recvTimer)
closeTimer := time.NewTimer(closeTime)
defer util.TimerStop(closeTimer)
for {
//intf.link.core.log.Debugf("State of %s: %s, source %s :: isAlive %t isReady %t sendTimerRunning %t recvTimerRunning %t",
// strings.ToUpper(intf.info.linkType), themString, intf.info.local,
// isAlive, isReady, sendTimerRunning, recvTimerRunning)
select {
case gotMsg, ok := <-signalAlive:
if !ok {
return
}
util.TimerStop(closeTimer)
closeTimer.Reset(closeTime)
util.TimerStop(recvTimer)
recvTimerRunning = false
isAlive = true
if !isReady {
// (Re-)enable in the switch
intf.link.core.switchTable.idleIn <- intf.peer.port
isReady = true
}
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
}
if !gotMsg {
intf.link.core.log.Tracef("Received ack from %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
}
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 {
// Disable in the switch
isReady = false
} else {
// Keep enabled in the switch
intf.link.core.switchTable.idleIn <- intf.peer.port
isReady = true
}
case <-sendBlocked.C:
// We blocked while trying to send something
isReady = false
intf.link.core.switchTable.blockPeer(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
intf.link.core.switchTable.blockPeer(intf.peer.port)
case <-closeTimer.C:
// We haven't received anything in a really long time, so things have died at the switch level and then some...
// Just close the connection at this point...
select {
case ret <- errors.New("timeout"):
default:
}
intf.msgIO.close()
}
}
}()
// Run reader loop
for {
msg, err := intf.msgIO.readMsg()
if len(msg) > 0 {
intf.peer.handlePacket(msg)
}
if err != nil {
if err != io.EOF {
select {
case ret <- err:
default:
}
}
break
}
select {
case signalAlive <- len(msg) > 0:
default:
}
}
////////////////////////////////////////////////////////////////////////////////
// Remember to set `err` to something useful before returning
select {
case err = <-ret:
intf.link.core.log.Infof("Disconnected %s: %s, source %s; error: %s", intf.link.core.log.Infof("Disconnected %s: %s, source %s; error: %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local, err) strings.ToUpper(intf.info.linkType), themString, intf.info.local, err)
default: } else {
err = nil
intf.link.core.log.Infof("Disconnected %s: %s, source %s", intf.link.core.log.Infof("Disconnected %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local) strings.ToUpper(intf.info.linkType), themString, intf.info.local)
} }
return err return err
} }
////////////////////////////////////////////////////////////////////////////////
const (
sendTime = 1 * time.Second // How long to wait before deciding a send is blocked
keepAliveTime = 2 * time.Second // How long to wait before sending a keep-alive response if we have no real traffic to send
stallTime = 6 * time.Second // How long to wait for response traffic before deciding the connection has stalled
closeTime = 2 * switch_timeout // How long to wait before closing the link
)
// notify the intf that we're currently sending
func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) {
intf.Act(&intf.writer, func() {
if !isLinkTraffic {
intf.inSwitch = false
}
intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend)
intf._cancelStallTimer()
})
}
// we just sent something, so cancel any pending timer to send keep-alive traffic
func (intf *linkInterface) _cancelStallTimer() {
if intf.stallTimer != nil {
intf.stallTimer.Stop()
intf.stallTimer = nil
}
}
// called by an AfterFunc if we appear to have timed out
func (intf *linkInterface) notifyBlockedSend() {
intf.Act(nil, func() { // Sent from a time.AfterFunc
if intf.sendTimer != nil {
//As far as we know, we're still trying to send, and the timer fired.
intf.link.core.switchTable.blockPeer(intf.peer.port)
}
})
}
// notify the intf that we've finished sending, returning the peer to the switch
func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) {
intf.Act(&intf.writer, func() {
intf.sendTimer.Stop()
intf.sendTimer = nil
if !isLinkTraffic {
intf._notifySwitch()
}
if size > 0 && intf.stallTimer == nil {
intf.stallTimer = time.AfterFunc(stallTime, intf.notifyStalled)
}
})
}
// Notify the switch that we're ready for more traffic, assuming we're not in a stalled state
func (intf *linkInterface) _notifySwitch() {
if !intf.inSwitch && !intf.stalled {
intf.inSwitch = true
intf.link.core.switchTable.Act(intf, func() {
intf.link.core.switchTable._idleIn(intf.peer.port)
})
}
}
// Set the peer as stalled, to prevent them from returning to the switch until a read succeeds
func (intf *linkInterface) notifyStalled() {
intf.Act(nil, func() { // Sent from a time.AfterFunc
if intf.stallTimer != nil {
intf.stallTimer.Stop()
intf.stallTimer = nil
intf.stalled = true
intf.link.core.switchTable.blockPeer(intf.peer.port)
}
})
}
// reset the close timer
func (intf *linkInterface) notifyReading() {
intf.Act(&intf.reader, func() {
if intf.closeTimer != nil {
intf.closeTimer.Stop()
}
intf.closeTimer = time.AfterFunc(closeTime, func() { intf.msgIO.close() })
})
}
// wake up the link if it was stalled, and (if size > 0) prepare to send keep-alive traffic
func (intf *linkInterface) notifyRead(size int) {
intf.Act(&intf.reader, func() {
if intf.stallTimer != nil {
intf.stallTimer.Stop()
intf.stallTimer = nil
}
intf.stalled = false
intf._notifySwitch()
if size > 0 && intf.stallTimer == nil {
intf.stallTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive)
}
})
}
// We need to send keep-alive traffic now
func (intf *linkInterface) notifyDoKeepAlive() {
intf.Act(nil, func() { // Sent from a time.AfterFunc
if intf.stallTimer != nil {
intf.stallTimer.Stop()
intf.stallTimer = nil
intf.writer.sendFrom(nil, [][]byte{nil}, true) // Empty keep-alive traffic
}
})
}
////////////////////////////////////////////////////////////////////////////////
type linkWriter struct {
phony.Inbox
intf *linkInterface
}
func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool) {
w.Act(from, func() {
var size int
for _, bs := range bss {
size += len(bs)
}
w.intf.notifySending(size, isLinkTraffic)
w.intf.msgIO.writeMsgs(bss)
w.intf.notifySent(size, isLinkTraffic)
// Cleanup
for _, bs := range bss {
util.PutBytes(bs)
}
})
}
////////////////////////////////////////////////////////////////////////////////
type linkReader struct {
phony.Inbox
intf *linkInterface
err chan error
}
func (r *linkReader) _read() {
r.intf.notifyReading()
msg, err := r.intf.msgIO.readMsg()
r.intf.notifyRead(len(msg))
if len(msg) > 0 {
r.intf.peer.handlePacketFrom(r, msg)
}
if err != nil {
if err != io.EOF {
r.err <- err
}
close(r.err)
return
}
// Now try to read again
r.Act(nil, r._read)
}

View File

@ -31,8 +31,8 @@ func (l *Listener) Close() (err error) {
recover() recover()
err = errors.New("already closed") err = errors.New("already closed")
}() }()
if l.core.sessions.listener == l { if l.core.router.sessions.listener == l {
l.core.sessions.listener = nil l.core.router.sessions.listener = nil
} }
close(l.close) close(l.close)
close(l.conn) close(l.conn)

View File

@ -47,25 +47,25 @@ func (m *nodeinfo) init(core *Core) {
m.callbacks = make(map[crypto.BoxPubKey]nodeinfoCallback) m.callbacks = make(map[crypto.BoxPubKey]nodeinfoCallback)
m.cache = make(map[crypto.BoxPubKey]nodeinfoCached) m.cache = make(map[crypto.BoxPubKey]nodeinfoCached)
go func() { var f func()
for { f = func() {
m.callbacksMutex.Lock() m.callbacksMutex.Lock()
for boxPubKey, callback := range m.callbacks { for boxPubKey, callback := range m.callbacks {
if time.Since(callback.created) > time.Minute { if time.Since(callback.created) > time.Minute {
delete(m.callbacks, boxPubKey) delete(m.callbacks, boxPubKey)
}
} }
m.callbacksMutex.Unlock()
m.cacheMutex.Lock()
for boxPubKey, cache := range m.cache {
if time.Since(cache.created) > time.Hour {
delete(m.cache, boxPubKey)
}
}
m.cacheMutex.Unlock()
time.Sleep(time.Second * 30)
} }
}() m.callbacksMutex.Unlock()
m.cacheMutex.Lock()
for boxPubKey, cache := range m.cache {
if time.Since(cache.created) > time.Hour {
delete(m.cache, boxPubKey)
}
}
m.cacheMutex.Unlock()
time.AfterFunc(time.Second*30, f)
}
go f()
} }
// Add a callback for a nodeinfo lookup // Add a callback for a nodeinfo lookup
@ -172,7 +172,7 @@ func (m *nodeinfo) sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse
NodeInfo: m.getNodeInfo(), NodeInfo: m.getNodeInfo(),
} }
bs := nodeinfo.encode() bs := nodeinfo.encode()
shared := m.core.sessions.getSharedKey(&m.core.boxPriv, &key) shared := m.core.router.sessions.getSharedKey(&m.core.boxPriv, &key)
payload, nonce := crypto.BoxSeal(shared, bs, nil) payload, nonce := crypto.BoxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{ p := wire_protoTrafficPacket{
Coords: coords, Coords: coords,

View File

@ -12,6 +12,8 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/Arceliar/phony"
) )
// The peers struct represents peers with an active connection. // The peers struct represents peers with an active connection.
@ -19,10 +21,9 @@ import (
// In most cases, this involves passing the packet to the handler for outgoing traffic to another peer. // 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. // 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.
type peers struct { type peers struct {
core *Core core *Core
reconfigure chan chan error mutex sync.Mutex // Synchronize writes to atomic
mutex sync.Mutex // Synchronize writes to atomic ports atomic.Value //map[switchPort]*peer, use CoW semantics
ports atomic.Value //map[switchPort]*peer, use CoW semantics
} }
// Initializes the peers struct. // Initializes the peers struct.
@ -31,13 +32,10 @@ func (ps *peers) init(c *Core) {
defer ps.mutex.Unlock() defer ps.mutex.Unlock()
ps.putPorts(make(map[switchPort]*peer)) ps.putPorts(make(map[switchPort]*peer))
ps.core = c ps.core = c
ps.reconfigure = make(chan chan error, 1) }
go func() {
for { func (ps *peers) reconfigure() {
e := <-ps.reconfigure // This is where reconfiguration would go, if we had anything to do
e <- nil
}
}()
} }
// Returns true if an incoming peer connection to a key is allowed, either // Returns true if an incoming peer connection to a key is allowed, either
@ -94,9 +92,7 @@ func (ps *peers) putPorts(ports map[switchPort]*peer) {
// Information known about a peer, including thier box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic // Information known about a peer, including thier box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic
type peer struct { type peer struct {
bytesSent uint64 // To track bandwidth usage for getPeers phony.Inbox
bytesRecvd uint64 // To track bandwidth usage for getPeers
// BUG: sync/atomic, 32 bit platforms need the above to be the first element
core *Core core *Core
intf *linkInterface intf *linkInterface
port switchPort port switchPort
@ -106,11 +102,14 @@ type peer struct {
linkShared crypto.BoxSharedKey linkShared crypto.BoxSharedKey
endpoint string endpoint string
firstSeen time.Time // To track uptime for getPeers firstSeen time.Time // To track uptime for getPeers
linkOut (chan []byte) // used for protocol traffic (to bypass queues) linkOut func([]byte) // used for protocol traffic (bypasses the switch)
doSend (chan struct{}) // tell the linkLoop to send a switchMsg dinfo *dhtInfo // used to keep the DHT working
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 out func([][]byte) // Set up by whatever created the peers struct, used to send packets to other nodes
done (chan struct{}) // closed to exit the linkLoop
close func() // Called when a peer is removed, to close the underlying connection, or via admin api close func() // Called when a peer is removed, to close the underlying connection, or via admin api
// The below aren't actually useful internally, they're just gathered for getPeers statistics
bytesSent uint64
bytesRecvd uint64
} }
// 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.
@ -121,8 +120,7 @@ func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShare
shared: *crypto.GetSharedKey(&ps.core.boxPriv, box), shared: *crypto.GetSharedKey(&ps.core.boxPriv, box),
linkShared: *linkShared, linkShared: *linkShared,
firstSeen: now, firstSeen: now,
doSend: make(chan struct{}, 1), done: make(chan struct{}),
dinfo: make(chan *dhtInfo, 1),
close: closer, close: closer,
core: ps.core, core: ps.core,
intf: intf, intf: intf,
@ -150,7 +148,7 @@ func (ps *peers) removePeer(port switchPort) {
if port == 0 { if port == 0 {
return return
} // Can't remove self peer } // Can't remove self peer
ps.core.router.doAdmin(func() { phony.Block(&ps.core.router, func() {
ps.core.switchTable.forgetPeer(port) ps.core.switchTable.forgetPeer(port)
}) })
ps.mutex.Lock() ps.mutex.Lock()
@ -167,103 +165,106 @@ func (ps *peers) removePeer(port switchPort) {
if p.close != nil { if p.close != nil {
p.close() p.close()
} }
close(p.doSend) close(p.done)
} }
} }
// If called, sends a notification to each peer that they should send a new switch message. // If called, sends a notification to each peer that they should send a new switch message.
// Mainly called by the switch after an update. // Mainly called by the switch after an update.
func (ps *peers) sendSwitchMsgs() { func (ps *peers) sendSwitchMsgs(from phony.Actor) {
ports := ps.getPorts() ports := ps.getPorts()
for _, p := range ports { for _, p := range ports {
if p.port == 0 { if p.port == 0 {
continue continue
} }
p.doSendSwitchMsgs() p.Act(from, p._sendSwitchMsg)
}
}
// 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. // This must be launched in a separate goroutine by whatever sets up the peer struct.
// It handles link protocol traffic. // It handles link protocol traffic.
func (p *peer) linkLoop() { func (p *peer) start() {
tick := time.NewTicker(time.Second) var updateDHT func()
defer tick.Stop() updateDHT = func() {
p.doSendSwitchMsgs() phony.Block(p, func() {
var dinfo *dhtInfo select {
for { case <-p.done:
select { default:
case _, ok := <-p.doSend: p._updateDHT()
if !ok { time.AfterFunc(time.Second, updateDHT)
return
} }
p.sendSwitchMsg() })
case dinfo = <-p.dinfo:
case _ = <-tick.C:
if dinfo != nil {
p.core.dht.peers <- dinfo
}
}
} }
updateDHT()
// 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. // Called to handle incoming packets.
// Passes the packet to a handler for that packet type. // Passes the packet to a handler for that packet type.
func (p *peer) handlePacket(packet []byte) { func (p *peer) _handlePacket(packet []byte) {
// FIXME this is off by stream padding and msg length overhead, should be done in tcp.go // FIXME this is off by stream padding and msg length overhead, should be done in tcp.go
atomic.AddUint64(&p.bytesRecvd, uint64(len(packet))) p.bytesRecvd += uint64(len(packet))
pType, pTypeLen := wire_decode_uint64(packet) pType, pTypeLen := wire_decode_uint64(packet)
if pTypeLen == 0 { if pTypeLen == 0 {
return return
} }
switch pType { switch pType {
case wire_Traffic: case wire_Traffic:
p.handleTraffic(packet, pTypeLen) p._handleTraffic(packet)
case wire_ProtocolTraffic: case wire_ProtocolTraffic:
p.handleTraffic(packet, pTypeLen) p._handleTraffic(packet)
case wire_LinkProtocolTraffic: case wire_LinkProtocolTraffic:
p.handleLinkTraffic(packet) p._handleLinkTraffic(packet)
default: default:
util.PutBytes(packet) util.PutBytes(packet)
} }
return
} }
// Called to handle traffic or protocolTraffic packets. // 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. // 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, pTypeLen int) { func (p *peer) _handleTraffic(packet []byte) {
table := p.core.switchTable.getTable() table := p.core.switchTable.getTable()
if _, isIn := table.elems[p.port]; !isIn && p.port != 0 { if _, isIn := table.elems[p.port]; !isIn && p.port != 0 {
// Drop traffic if the peer isn't in the switch // Drop traffic if the peer isn't in the switch
return return
} }
p.core.switchTable.packetIn <- packet p.core.switchTable.packetInFrom(p, packet)
}
func (p *peer) sendPacketsFrom(from phony.Actor, packets [][]byte) {
p.Act(from, func() {
p._sendPackets(packets)
})
} }
// This just calls p.out(packet) for now. // This just calls p.out(packet) for now.
func (p *peer) sendPackets(packets [][]byte) { func (p *peer) _sendPackets(packets [][]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?
var size int var size int
for _, packet := range packets { for _, packet := range packets {
size += len(packet) size += len(packet)
} }
atomic.AddUint64(&p.bytesSent, uint64(size)) p.bytesSent += uint64(size)
p.out(packets) p.out(packets)
} }
// This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers. // 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. // It sends it to p.linkOut, which bypasses the usual packet queues.
func (p *peer) sendLinkPacket(packet []byte) { func (p *peer) _sendLinkPacket(packet []byte) {
innerPayload, innerNonce := crypto.BoxSeal(&p.linkShared, packet, nil) innerPayload, innerNonce := crypto.BoxSeal(&p.linkShared, packet, nil)
innerLinkPacket := wire_linkProtoTrafficPacket{ innerLinkPacket := wire_linkProtoTrafficPacket{
Nonce: *innerNonce, Nonce: *innerNonce,
@ -276,12 +277,12 @@ func (p *peer) sendLinkPacket(packet []byte) {
Payload: bs, Payload: bs,
} }
packet = linkPacket.encode() packet = linkPacket.encode()
p.linkOut <- packet p.linkOut(packet)
} }
// Decrypts the outer (permanent) and inner (ephemeral) crypto layers on link traffic. // Decrypts the outer (permanent) and inner (ephemeral) crypto layers on link traffic.
// Identifies the link traffic type and calls the appropriate handler. // Identifies the link traffic type and calls the appropriate handler.
func (p *peer) handleLinkTraffic(bs []byte) { func (p *peer) _handleLinkTraffic(bs []byte) {
packet := wire_linkProtoTrafficPacket{} packet := wire_linkProtoTrafficPacket{}
if !packet.decode(bs) { if !packet.decode(bs) {
return return
@ -304,14 +305,14 @@ func (p *peer) handleLinkTraffic(bs []byte) {
} }
switch pType { switch pType {
case wire_SwitchMsg: case wire_SwitchMsg:
p.handleSwitchMsg(payload) p._handleSwitchMsg(payload)
default: default:
util.PutBytes(bs) util.PutBytes(bs)
} }
} }
// Gets a switchMsg from the switch, adds signed next-hop info for this peer, and sends it to them. // Gets a switchMsg from the switch, adds signed next-hop info for this peer, and sends it to them.
func (p *peer) sendSwitchMsg() { func (p *peer) _sendSwitchMsg() {
msg := p.core.switchTable.getMsg() msg := p.core.switchTable.getMsg()
if msg == nil { if msg == nil {
return return
@ -323,12 +324,12 @@ func (p *peer) sendSwitchMsg() {
Sig: *crypto.Sign(&p.core.sigPriv, bs), Sig: *crypto.Sign(&p.core.sigPriv, bs),
}) })
packet := msg.encode() packet := msg.encode()
p.sendLinkPacket(packet) p._sendLinkPacket(packet)
} }
// Handles a switchMsg from the peer, checking signatures and passing good messages to the switch. // 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). // 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) { func (p *peer) _handleSwitchMsg(packet []byte) {
var msg switchMsg var msg switchMsg
if !msg.decode(packet) { if !msg.decode(packet) {
return return
@ -352,16 +353,16 @@ func (p *peer) handleSwitchMsg(packet []byte) {
p.core.switchTable.handleMsg(&msg, p.port) p.core.switchTable.handleMsg(&msg, p.port)
if !p.core.switchTable.checkRoot(&msg) { if !p.core.switchTable.checkRoot(&msg) {
// Bad switch message // Bad switch message
p.dinfo <- nil p.dinfo = nil
return return
} }
// Pass a mesage to the dht informing it that this peer (still) exists // Pass a mesage to the dht informing it that this peer (still) exists
loc.coords = loc.coords[:len(loc.coords)-1] loc.coords = loc.coords[:len(loc.coords)-1]
dinfo := dhtInfo{ p.dinfo = &dhtInfo{
key: p.box, key: p.box,
coords: loc.getCoords(), coords: loc.getCoords(),
} }
p.dinfo <- &dinfo p._updateDHT()
} }
// This generates the bytes that we sign or check the signature of for a switchMsg. // This generates the bytes that we sign or check the signature of for a switchMsg.

View File

@ -30,29 +30,29 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/address" "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" "github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/Arceliar/phony"
) )
// The router struct has channels to/from the adapter device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. // The router struct has channels to/from the adapter device and a self peer (0), which is how messages are passed between this node and the peers/switch layer.
// The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. // The router's phony.Inbox goroutine is responsible for managing all information related to the dht, searches, and crypto sessions.
type router struct { type router struct {
core *Core phony.Inbox
reconfigure chan chan error core *Core
addr address.Address addr address.Address
subnet address.Subnet subnet address.Subnet
in <-chan [][]byte // packets we received from the network, link to peer's "out" out func([]byte) // packets we're sending to the network, link to peer's "in"
out func([]byte) // packets we're sending to the network, link to peer's "in" dht dht
reset chan struct{} // signal that coords changed (re-init sessions/dht) nodeinfo nodeinfo
admin chan func() // pass a lambda for the admin socket to query stuff searches searches
nodeinfo nodeinfo sessions sessions
} }
// Initializes the router struct, which includes setting up channels to/from the adapter. // Initializes the router struct, which includes setting up channels to/from the adapter.
func (r *router) init(core *Core) { func (r *router) init(core *Core) {
r.core = core r.core = core
r.reconfigure = make(chan chan error, 1) r.addr = *address.AddrForNodeID(&r.dht.nodeID)
r.addr = *address.AddrForNodeID(&r.core.dht.nodeID) r.subnet = *address.SubnetForNodeID(&r.dht.nodeID)
r.subnet = *address.SubnetForNodeID(&r.core.dht.nodeID)
in := make(chan [][]byte, 1) // TODO something better than this...
self := linkInterface{ self := linkInterface{
name: "(self)", name: "(self)",
info: linkInfo{ info: linkInfo{
@ -62,120 +62,109 @@ func (r *router) init(core *Core) {
}, },
} }
p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil) p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil)
p.out = func(packets [][]byte) { in <- packets } p.out = func(packets [][]byte) { r.handlePackets(p, packets) }
r.in = in r.out = func(bs []byte) { p.handlePacketFrom(r, bs) }
out := make(chan []byte, 32)
go func() {
for packet := range out {
p.handlePacket(packet)
}
}()
out2 := make(chan []byte, 32)
go func() {
// This worker makes sure r.out never blocks
// It will buffer traffic long enough for the switch worker to take it
// If (somehow) you can send faster than the switch can receive, then this would use unbounded memory
// But crypto slows sends enough that the switch should always be able to take the packets...
var buf [][]byte
for {
buf = append(buf, <-out2)
for len(buf) > 0 {
select {
case bs := <-out2:
buf = append(buf, bs)
case out <- buf[0]:
buf = buf[1:]
}
}
}
}()
r.out = func(packet []byte) { out2 <- packet }
r.reset = make(chan struct{}, 1)
r.admin = make(chan func(), 32)
r.nodeinfo.init(r.core) r.nodeinfo.init(r.core)
r.core.config.Mutex.RLock() r.core.config.Mutex.RLock()
r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy)
r.core.config.Mutex.RUnlock() r.core.config.Mutex.RUnlock()
r.dht.init(r)
r.searches.init(r)
r.sessions.init(r)
} }
// Starts the mainLoop goroutine. // Reconfigures the router and any child modules. This should only ever be run
// by the router actor.
func (r *router) reconfigure() {
// Reconfigure the router
current := r.core.config.GetCurrent()
if err := r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy); err != nil {
r.core.log.Errorln("Error reloading NodeInfo:", err)
} else {
r.core.log.Infoln("NodeInfo updated")
}
// Reconfigure children
r.dht.reconfigure()
r.searches.reconfigure()
r.sessions.reconfigure()
}
// Starts the tickerLoop goroutine.
func (r *router) start() error { func (r *router) start() error {
r.core.log.Infoln("Starting router") r.core.log.Infoln("Starting router")
go r.mainLoop() go r.doMaintenance()
return nil return nil
} }
// Takes traffic from the adapter and passes it to router.send, or from r.in and handles incoming traffic. // In practice, the switch will call this with 1 packet
// Also adds new peer info to the DHT. func (r *router) handlePackets(from phony.Actor, packets [][]byte) {
// Also resets the DHT and sesssions in the event of a coord change. r.Act(from, func() {
// Also does periodic maintenance stuff. for _, packet := range packets {
func (r *router) mainLoop() { r._handlePacket(packet)
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case ps := <-r.in:
for _, p := range ps {
r.handleIn(p)
}
case info := <-r.core.dht.peers:
r.core.dht.insertPeer(info)
case <-r.reset:
r.core.sessions.reset()
r.core.dht.reset()
case <-ticker.C:
{
// Any periodic maintenance stuff goes here
r.core.switchTable.doMaintenance()
r.core.dht.doMaintenance()
r.core.sessions.cleanup()
}
case f := <-r.admin:
f()
case e := <-r.reconfigure:
current := r.core.config.GetCurrent()
e <- r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy)
} }
} })
}
// Insert a peer info into the dht, TODO? make the dht a separate actor
func (r *router) insertPeer(from phony.Actor, info *dhtInfo) {
r.Act(from, func() {
r.dht.insertPeer(info)
})
}
// Reset sessions and DHT after the switch sees our coords change
func (r *router) reset(from phony.Actor) {
r.Act(from, func() {
r.sessions.reset()
r.dht.reset()
})
}
// TODO remove reconfigure so this is just a ticker loop
// and then find something better than a ticker loop to schedule things...
func (r *router) doMaintenance() {
phony.Block(r, func() {
// Any periodic maintenance stuff goes here
r.core.switchTable.doMaintenance()
r.dht.doMaintenance()
r.sessions.cleanup()
})
time.AfterFunc(time.Second, r.doMaintenance)
} }
// Checks incoming traffic type and passes it to the appropriate handler. // Checks incoming traffic type and passes it to the appropriate handler.
func (r *router) handleIn(packet []byte) { func (r *router) _handlePacket(packet []byte) {
pType, pTypeLen := wire_decode_uint64(packet) pType, pTypeLen := wire_decode_uint64(packet)
if pTypeLen == 0 { if pTypeLen == 0 {
return return
} }
switch pType { switch pType {
case wire_Traffic: case wire_Traffic:
r.handleTraffic(packet) r._handleTraffic(packet)
case wire_ProtocolTraffic: case wire_ProtocolTraffic:
r.handleProto(packet) r._handleProto(packet)
default: default:
} }
} }
// Handles incoming traffic, i.e. encapuslated ordinary IPv6 packets. // Handles incoming traffic, i.e. encapuslated ordinary IPv6 packets.
// Passes them to the crypto session worker to be decrypted and sent to the adapter. // Passes them to the crypto session worker to be decrypted and sent to the adapter.
func (r *router) handleTraffic(packet []byte) { func (r *router) _handleTraffic(packet []byte) {
defer util.PutBytes(packet) defer util.PutBytes(packet)
p := wire_trafficPacket{} p := wire_trafficPacket{}
if !p.decode(packet) { if !p.decode(packet) {
return return
} }
sinfo, isIn := r.core.sessions.getSessionForHandle(&p.Handle) sinfo, isIn := r.sessions.getSessionForHandle(&p.Handle)
if !isIn { if !isIn {
util.PutBytes(p.Payload) util.PutBytes(p.Payload)
return return
} }
select { sinfo.recv(r, &p)
case sinfo.fromRouter <- p:
case <-sinfo.cancel.Finished():
util.PutBytes(p.Payload)
}
} }
// Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type. // Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type.
func (r *router) handleProto(packet []byte) { func (r *router) _handleProto(packet []byte) {
// First parse the packet // First parse the packet
p := wire_protoTrafficPacket{} p := wire_protoTrafficPacket{}
if !p.decode(packet) { if !p.decode(packet) {
@ -185,7 +174,7 @@ func (r *router) handleProto(packet []byte) {
var sharedKey *crypto.BoxSharedKey var sharedKey *crypto.BoxSharedKey
if p.ToKey == r.core.boxPub { if p.ToKey == r.core.boxPub {
// Try to open using our permanent key // Try to open using our permanent key
sharedKey = r.core.sessions.getSharedKey(&r.core.boxPriv, &p.FromKey) sharedKey = r.sessions.getSharedKey(&r.core.boxPriv, &p.FromKey)
} else { } else {
return return
} }
@ -202,59 +191,59 @@ func (r *router) handleProto(packet []byte) {
} }
switch bsType { switch bsType {
case wire_SessionPing: case wire_SessionPing:
r.handlePing(bs, &p.FromKey) r._handlePing(bs, &p.FromKey)
case wire_SessionPong: case wire_SessionPong:
r.handlePong(bs, &p.FromKey) r._handlePong(bs, &p.FromKey)
case wire_NodeInfoRequest: case wire_NodeInfoRequest:
fallthrough fallthrough
case wire_NodeInfoResponse: case wire_NodeInfoResponse:
r.handleNodeInfo(bs, &p.FromKey) r._handleNodeInfo(bs, &p.FromKey)
case wire_DHTLookupRequest: case wire_DHTLookupRequest:
r.handleDHTReq(bs, &p.FromKey) r._handleDHTReq(bs, &p.FromKey)
case wire_DHTLookupResponse: case wire_DHTLookupResponse:
r.handleDHTRes(bs, &p.FromKey) r._handleDHTRes(bs, &p.FromKey)
default: default:
util.PutBytes(packet) util.PutBytes(packet)
} }
} }
// Decodes session pings from wire format and passes them to sessions.handlePing where they either create or update a session. // Decodes session pings from wire format and passes them to sessions.handlePing where they either create or update a session.
func (r *router) handlePing(bs []byte, fromKey *crypto.BoxPubKey) { func (r *router) _handlePing(bs []byte, fromKey *crypto.BoxPubKey) {
ping := sessionPing{} ping := sessionPing{}
if !ping.decode(bs) { if !ping.decode(bs) {
return return
} }
ping.SendPermPub = *fromKey ping.SendPermPub = *fromKey
r.core.sessions.handlePing(&ping) r.sessions.handlePing(&ping)
} }
// Handles session pongs (which are really pings with an extra flag to prevent acknowledgement). // Handles session pongs (which are really pings with an extra flag to prevent acknowledgement).
func (r *router) handlePong(bs []byte, fromKey *crypto.BoxPubKey) { func (r *router) _handlePong(bs []byte, fromKey *crypto.BoxPubKey) {
r.handlePing(bs, fromKey) r._handlePing(bs, fromKey)
} }
// Decodes dht requests and passes them to dht.handleReq to trigger a lookup/response. // Decodes dht requests and passes them to dht.handleReq to trigger a lookup/response.
func (r *router) handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey) { func (r *router) _handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey) {
req := dhtReq{} req := dhtReq{}
if !req.decode(bs) { if !req.decode(bs) {
return return
} }
req.Key = *fromKey req.Key = *fromKey
r.core.dht.handleReq(&req) r.dht.handleReq(&req)
} }
// Decodes dht responses and passes them to dht.handleRes to update the DHT table and further pass them to the search code (if applicable). // Decodes dht responses and passes them to dht.handleRes to update the DHT table and further pass them to the search code (if applicable).
func (r *router) handleDHTRes(bs []byte, fromKey *crypto.BoxPubKey) { func (r *router) _handleDHTRes(bs []byte, fromKey *crypto.BoxPubKey) {
res := dhtRes{} res := dhtRes{}
if !res.decode(bs) { if !res.decode(bs) {
return return
} }
res.Key = *fromKey res.Key = *fromKey
r.core.dht.handleRes(&res) r.dht.handleRes(&res)
} }
// Decodes nodeinfo request // Decodes nodeinfo request
func (r *router) handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { func (r *router) _handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) {
req := nodeinfoReqRes{} req := nodeinfoReqRes{}
if !req.decode(bs) { if !req.decode(bs) {
return return
@ -262,18 +251,3 @@ func (r *router) handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) {
req.SendPermPub = *fromKey req.SendPermPub = *fromKey
r.nodeinfo.handleNodeInfo(&req) r.nodeinfo.handleNodeInfo(&req)
} }
// Passed a function to call.
// This will send the function to r.admin and block until it finishes.
// It's used by the admin socket to ask the router mainLoop goroutine about information in the session or dht structs, which cannot be read safely from outside that goroutine.
func (r *router) doAdmin(f func()) {
// Pass this a function that needs to be run by the router's main goroutine
// It will pass the function to the router and wait for the router to finish
done := make(chan struct{})
newF := func() {
f()
close(done)
}
r.admin <- newF
<-done
}

View File

@ -33,7 +33,7 @@ const search_RETRY_TIME = time.Second
// Information about an ongoing search. // Information about an ongoing search.
// Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
type searchInfo struct { type searchInfo struct {
core *Core searches *searches
dest crypto.NodeID dest crypto.NodeID
mask crypto.NodeID mask crypto.NodeID
time time.Time time time.Time
@ -45,28 +45,24 @@ type searchInfo struct {
// This stores a map of active searches. // This stores a map of active searches.
type searches struct { type searches struct {
core *Core router *router
reconfigure chan chan error searches map[crypto.NodeID]*searchInfo
searches map[crypto.NodeID]*searchInfo
} }
// Initializes the searches struct. // Initializes the searches struct.
func (s *searches) init(core *Core) { func (s *searches) init(r *router) {
s.core = core s.router = r
s.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-s.reconfigure
e <- nil
}
}()
s.searches = make(map[crypto.NodeID]*searchInfo) s.searches = make(map[crypto.NodeID]*searchInfo)
} }
func (s *searches) reconfigure() {
// This is where reconfiguration would go, if we had anything to do
}
// Creates a new search info, adds it to the searches struct, and returns a pointer to the info. // Creates a new search info, adds it to the searches struct, and returns a pointer to the info.
func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
info := searchInfo{ info := searchInfo{
core: s.core, searches: s,
dest: *dest, dest: *dest,
mask: *mask, mask: *mask,
time: time.Now(), time: time.Now(),
@ -100,7 +96,7 @@ func (sinfo *searchInfo) addToSearch(res *dhtRes) {
from := dhtInfo{key: res.Key, coords: res.Coords} from := dhtInfo{key: res.Key, coords: res.Coords}
sinfo.visited[*from.getNodeID()] = true sinfo.visited[*from.getNodeID()] = true
for _, info := range res.Infos { for _, info := range res.Infos {
if *info.getNodeID() == sinfo.core.dht.nodeID || sinfo.visited[*info.getNodeID()] { if *info.getNodeID() == sinfo.searches.router.dht.nodeID || sinfo.visited[*info.getNodeID()] {
continue continue
} }
if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) { if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) {
@ -134,7 +130,7 @@ func (sinfo *searchInfo) doSearchStep() {
if len(sinfo.toVisit) == 0 { if len(sinfo.toVisit) == 0 {
if time.Since(sinfo.time) > search_RETRY_TIME { if time.Since(sinfo.time) > search_RETRY_TIME {
// Dead end and no response in too long, do cleanup // Dead end and no response in too long, do cleanup
delete(sinfo.core.searches.searches, sinfo.dest) delete(sinfo.searches.searches, sinfo.dest)
sinfo.callback(nil, errors.New("search reached dead end")) sinfo.callback(nil, errors.New("search reached dead end"))
} }
return return
@ -143,8 +139,8 @@ func (sinfo *searchInfo) doSearchStep() {
var next *dhtInfo var next *dhtInfo
next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
rq := dhtReqKey{next.key, sinfo.dest} rq := dhtReqKey{next.key, sinfo.dest}
sinfo.core.dht.addCallback(&rq, sinfo.handleDHTRes) sinfo.searches.router.dht.addCallback(&rq, sinfo.handleDHTRes)
sinfo.core.dht.ping(next, &sinfo.dest) sinfo.searches.router.dht.ping(next, &sinfo.dest)
sinfo.time = time.Now() sinfo.time = time.Now()
} }
@ -155,27 +151,25 @@ func (sinfo *searchInfo) continueSearch() {
// In case the search dies, try to spawn another thread later // In case the search dies, try to spawn another thread later
// Note that this will spawn multiple parallel searches as time passes // Note that this will spawn multiple parallel searches as time passes
// Any that die aren't restarted, but a new one will start later // Any that die aren't restarted, but a new one will start later
retryLater := func() { time.AfterFunc(search_RETRY_TIME, func() {
// FIXME this keeps the search alive forever if not for the searches map, fix that sinfo.searches.router.Act(nil, func() {
newSearchInfo := sinfo.core.searches.searches[sinfo.dest] // FIXME this keeps the search alive forever if not for the searches map, fix that
if newSearchInfo != sinfo { newSearchInfo := sinfo.searches.searches[sinfo.dest]
return if newSearchInfo != sinfo {
} return
sinfo.continueSearch() }
} sinfo.continueSearch()
go func() { })
time.Sleep(search_RETRY_TIME) })
sinfo.core.router.admin <- retryLater
}()
} }
// Calls create search, and initializes the iterative search parts of the struct before returning it. // Calls create search, and initializes the iterative search parts of the struct before returning it.
func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
sinfo := s.createSearch(dest, mask, callback) sinfo := s.createSearch(dest, mask, callback)
sinfo.visited = make(map[crypto.NodeID]bool) sinfo.visited = make(map[crypto.NodeID]bool)
loc := s.core.switchTable.getLocator() loc := s.router.core.switchTable.getLocator()
sinfo.toVisit = append(sinfo.toVisit, &dhtInfo{ sinfo.toVisit = append(sinfo.toVisit, &dhtInfo{
key: s.core.boxPub, key: s.router.core.boxPub,
coords: loc.getCoords(), coords: loc.getCoords(),
}) // Start the search by asking ourself, useful if we're the destination }) // Start the search by asking ourself, useful if we're the destination
return sinfo return sinfo
@ -196,26 +190,26 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool {
return false return false
} }
// They match, so create a session and send a sessionRequest // They match, so create a session and send a sessionRequest
sess, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key) sess, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key)
if !isIn { if !isIn {
sess = sinfo.core.sessions.createSession(&res.Key) sess = sinfo.searches.router.sessions.createSession(&res.Key)
if sess == nil { if sess == nil {
// nil if the DHT search finished but the session wasn't allowed // nil if the DHT search finished but the session wasn't allowed
sinfo.callback(nil, errors.New("session not allowed")) sinfo.callback(nil, errors.New("session not allowed"))
// Cleanup // Cleanup
delete(sinfo.core.searches.searches, res.Dest) delete(sinfo.searches.searches, res.Dest)
return true return true
} }
_, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key) _, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key)
if !isIn { if !isIn {
panic("This should never happen") panic("This should never happen")
} }
} }
// FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)?
sess.coords = res.Coords sess.coords = res.Coords
sinfo.core.sessions.ping(sess) sess.ping(sinfo.searches.router)
sinfo.callback(sess, nil) sinfo.callback(sess, nil)
// Cleanup // Cleanup
delete(sinfo.core.searches.searches, res.Dest) delete(sinfo.searches.searches, res.Dest)
return true return true
} }

View File

@ -7,13 +7,14 @@ package yggdrasil
import ( import (
"bytes" "bytes"
"container/heap" "container/heap"
"errors"
"sync" "sync"
"time" "time"
"github.com/yggdrasil-network/yggdrasil-go/src/address" "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" "github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/Arceliar/phony"
) )
// Duration that we keep track of old nonces per session, to allow some out-of-order packet delivery // Duration that we keep track of old nonces per session, to allow some out-of-order packet delivery
@ -37,15 +38,15 @@ func (h nonceHeap) peek() *crypto.BoxNonce { return &h[0] }
// All the information we know about an active session. // All the information we know about an active session.
// This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API.
type sessionInfo struct { type sessionInfo struct {
mutex sync.Mutex // Protects all of the below, use it any time you read/chance the contents of a session phony.Inbox // Protects all of the below, use it any time you read/change the contents of a session
core *Core // sessions *sessions //
reconfigure chan chan error //
theirAddr address.Address // theirAddr address.Address //
theirSubnet address.Subnet // theirSubnet address.Subnet //
theirPermPub crypto.BoxPubKey // theirPermPub crypto.BoxPubKey //
theirSesPub crypto.BoxPubKey // theirSesPub crypto.BoxPubKey //
mySesPub crypto.BoxPubKey // mySesPub crypto.BoxPubKey //
mySesPriv crypto.BoxPrivKey // mySesPriv crypto.BoxPrivKey //
sharedPermKey crypto.BoxSharedKey // used for session pings
sharedSesKey crypto.BoxSharedKey // derived from session keys sharedSesKey crypto.BoxSharedKey // derived from session keys
theirHandle crypto.Handle // theirHandle crypto.Handle //
myHandle crypto.Handle // myHandle crypto.Handle //
@ -68,15 +69,12 @@ type sessionInfo struct {
bytesRecvd uint64 // Bytes of real traffic received in this session bytesRecvd uint64 // Bytes of real traffic received in this session
init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use
cancel util.Cancellation // Used to terminate workers cancel util.Cancellation // Used to terminate workers
fromRouter chan wire_trafficPacket // Received packets go here, to be decrypted by the session conn *Conn // The associated Conn object
recv chan []byte // Decrypted packets go here, picked up by the associated Conn callbacks []chan func() // Finished work from crypto workers
send chan FlowKeyMessage // Packets with optional flow key go here, to be encrypted and sent
} }
func (sinfo *sessionInfo) doFunc(f func()) { func (sinfo *sessionInfo) reconfigure() {
sinfo.mutex.Lock() // This is where reconfiguration would go, if we had anything to do
defer sinfo.mutex.Unlock()
f()
} }
// Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU.
@ -92,7 +90,7 @@ type sessionPing struct {
// Updates session info in response to a ping, after checking that the ping is OK. // Updates session info in response to a ping, after checking that the ping is OK.
// Returns true if the session was updated, or false otherwise. // Returns true if the session was updated, or false otherwise.
func (s *sessionInfo) update(p *sessionPing) bool { func (s *sessionInfo) _update(p *sessionPing) bool {
if !(p.Tstamp > s.tstamp) { if !(p.Tstamp > s.tstamp) {
// To protect against replay attacks // To protect against replay attacks
return false return false
@ -112,6 +110,9 @@ func (s *sessionInfo) update(p *sessionPing) bool {
} }
if p.MTU >= 1280 || p.MTU == 0 { if p.MTU >= 1280 || p.MTU == 0 {
s.theirMTU = p.MTU s.theirMTU = p.MTU
if s.conn != nil {
s.conn.setMTU(s, s._getMTU())
}
} }
if !bytes.Equal(s.coords, p.Coords) { if !bytes.Equal(s.coords, p.Coords) {
// allocate enough space for additional coords // allocate enough space for additional coords
@ -134,10 +135,9 @@ func (s *sessionInfo) update(p *sessionPing) bool {
// Sessions are indexed by handle. // Sessions are indexed by handle.
// Additionally, stores maps of address/subnet onto keys, and keys onto handles. // Additionally, stores maps of address/subnet onto keys, and keys onto handles.
type sessions struct { type sessions struct {
core *Core router *router
listener *Listener listener *Listener
listenerMutex sync.Mutex listenerMutex sync.Mutex
reconfigure chan chan error
lastCleanup time.Time lastCleanup time.Time
isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed
isAllowedMutex sync.RWMutex // Protects the above isAllowedMutex sync.RWMutex // Protects the above
@ -147,32 +147,20 @@ type sessions struct {
} }
// Initializes the session struct. // Initializes the session struct.
func (ss *sessions) init(core *Core) { func (ss *sessions) init(r *router) {
ss.core = core ss.router = r
ss.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-ss.reconfigure
responses := make(map[crypto.Handle]chan error)
for index, session := range ss.sinfos {
responses[index] = make(chan error)
session.reconfigure <- responses[index]
}
for _, response := range responses {
if err := <-response; err != nil {
e <- err
continue
}
}
e <- nil
}
}()
ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey) ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey)
ss.sinfos = make(map[crypto.Handle]*sessionInfo) ss.sinfos = make(map[crypto.Handle]*sessionInfo)
ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle) ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle)
ss.lastCleanup = time.Now() ss.lastCleanup = time.Now()
} }
func (ss *sessions) reconfigure() {
for _, session := range ss.sinfos {
session.reconfigure()
}
}
// Determines whether the session with a given publickey is allowed based on // Determines whether the session with a given publickey is allowed based on
// session firewall rules. // session firewall rules.
func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool { func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool {
@ -211,17 +199,17 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
return nil return nil
} }
sinfo := sessionInfo{} sinfo := sessionInfo{}
sinfo.core = ss.core sinfo.sessions = ss
sinfo.reconfigure = make(chan chan error, 1)
sinfo.theirPermPub = *theirPermKey sinfo.theirPermPub = *theirPermKey
sinfo.sharedPermKey = *ss.getSharedKey(&ss.router.core.boxPriv, &sinfo.theirPermPub)
pub, priv := crypto.NewBoxKeys() pub, priv := crypto.NewBoxKeys()
sinfo.mySesPub = *pub sinfo.mySesPub = *pub
sinfo.mySesPriv = *priv sinfo.mySesPriv = *priv
sinfo.myNonce = *crypto.NewBoxNonce() sinfo.myNonce = *crypto.NewBoxNonce()
sinfo.theirMTU = 1280 sinfo.theirMTU = 1280
ss.core.config.Mutex.RLock() ss.router.core.config.Mutex.RLock()
sinfo.myMTU = uint16(ss.core.config.Current.IfMTU) sinfo.myMTU = uint16(ss.router.core.config.Current.IfMTU)
ss.core.config.Mutex.RUnlock() ss.router.core.config.Mutex.RUnlock()
now := time.Now() now := time.Now()
sinfo.timeOpened = now sinfo.timeOpened = now
sinfo.time = now sinfo.time = now
@ -231,11 +219,11 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
sinfo.init = make(chan struct{}) sinfo.init = make(chan struct{})
sinfo.cancel = util.NewCancellation() sinfo.cancel = util.NewCancellation()
higher := false higher := false
for idx := range ss.core.boxPub { for idx := range ss.router.core.boxPub {
if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] { if ss.router.core.boxPub[idx] > sinfo.theirPermPub[idx] {
higher = true higher = true
break break
} else if ss.core.boxPub[idx] < sinfo.theirPermPub[idx] { } else if ss.router.core.boxPub[idx] < sinfo.theirPermPub[idx] {
break break
} }
} }
@ -249,17 +237,8 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
sinfo.myHandle = *crypto.NewHandle() sinfo.myHandle = *crypto.NewHandle()
sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
sinfo.fromRouter = make(chan wire_trafficPacket, 1)
sinfo.recv = make(chan []byte, 32)
sinfo.send = make(chan FlowKeyMessage, 32)
ss.sinfos[sinfo.myHandle] = &sinfo ss.sinfos[sinfo.myHandle] = &sinfo
ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle
go func() {
// Run cleanup when the session is canceled
<-sinfo.cancel.Finished()
sinfo.core.router.doAdmin(sinfo.close)
}()
go sinfo.startWorkers()
return &sinfo return &sinfo
} }
@ -291,20 +270,26 @@ func (ss *sessions) cleanup() {
ss.lastCleanup = time.Now() ss.lastCleanup = time.Now()
} }
func (sinfo *sessionInfo) doRemove() {
sinfo.sessions.router.Act(nil, func() {
sinfo.sessions.removeSession(sinfo)
})
}
// Closes a session, removing it from sessions maps. // Closes a session, removing it from sessions maps.
func (sinfo *sessionInfo) close() { func (ss *sessions) removeSession(sinfo *sessionInfo) {
if s := sinfo.core.sessions.sinfos[sinfo.myHandle]; s == sinfo { if s := sinfo.sessions.sinfos[sinfo.myHandle]; s == sinfo {
delete(sinfo.core.sessions.sinfos, sinfo.myHandle) delete(sinfo.sessions.sinfos, sinfo.myHandle)
delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) delete(sinfo.sessions.byTheirPerm, sinfo.theirPermPub)
} }
} }
// Returns a session ping appropriate for the given session info. // Returns a session ping appropriate for the given session info.
func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing { func (sinfo *sessionInfo) _getPing() sessionPing {
loc := ss.core.switchTable.getLocator() loc := sinfo.sessions.router.core.switchTable.getLocator()
coords := loc.getCoords() coords := loc.getCoords()
ref := sessionPing{ ping := sessionPing{
SendPermPub: ss.core.boxPub, SendPermPub: sinfo.sessions.router.core.boxPub,
Handle: sinfo.myHandle, Handle: sinfo.myHandle,
SendSesPub: sinfo.mySesPub, SendSesPub: sinfo.mySesPub,
Tstamp: time.Now().Unix(), Tstamp: time.Now().Unix(),
@ -312,7 +297,7 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing {
MTU: sinfo.myMTU, MTU: sinfo.myMTU,
} }
sinfo.myNonce.Increment() sinfo.myNonce.Increment()
return ref return ping
} }
// Gets the shared key for a pair of box keys. // Gets the shared key for a pair of box keys.
@ -339,41 +324,50 @@ func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey,
} }
// Sends a session ping by calling sendPingPong in ping mode. // Sends a session ping by calling sendPingPong in ping mode.
func (ss *sessions) ping(sinfo *sessionInfo) { func (sinfo *sessionInfo) ping(from phony.Actor) {
ss.sendPingPong(sinfo, false) sinfo.Act(from, func() {
sinfo._sendPingPong(false)
})
} }
// Calls getPing, sets the appropriate ping/pong flag, encodes to wire format, and send it. // Calls getPing, sets the appropriate ping/pong flag, encodes to wire format, and send it.
// Updates the time the last ping was sent in the session info. // Updates the time the last ping was sent in the session info.
func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { func (sinfo *sessionInfo) _sendPingPong(isPong bool) {
ping := ss.getPing(sinfo) ping := sinfo._getPing()
ping.IsPong = isPong ping.IsPong = isPong
bs := ping.encode() bs := ping.encode()
shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) payload, nonce := crypto.BoxSeal(&sinfo.sharedPermKey, bs, nil)
payload, nonce := crypto.BoxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{ p := wire_protoTrafficPacket{
Coords: sinfo.coords, Coords: sinfo.coords,
ToKey: sinfo.theirPermPub, ToKey: sinfo.theirPermPub,
FromKey: ss.core.boxPub, FromKey: sinfo.sessions.router.core.boxPub,
Nonce: *nonce, Nonce: *nonce,
Payload: payload, Payload: payload,
} }
packet := p.encode() packet := p.encode()
ss.core.router.out(packet) // TODO rewrite the below if/when the peer struct becomes an actor, to not go through the router first
sinfo.sessions.router.Act(sinfo, func() { sinfo.sessions.router.out(packet) })
if sinfo.pingTime.Before(sinfo.time) { if sinfo.pingTime.Before(sinfo.time) {
sinfo.pingTime = time.Now() sinfo.pingTime = time.Now()
} }
} }
func (sinfo *sessionInfo) setConn(from phony.Actor, conn *Conn) {
sinfo.Act(from, func() {
sinfo.conn = conn
sinfo.conn.setMTU(sinfo, sinfo._getMTU())
})
}
// Handles a session ping, creating a session if needed and calling update, then possibly responding with a pong if the ping was in ping mode and the update was successful. // Handles a session ping, creating a session if needed and calling update, then possibly responding with a pong if the ping was in ping mode and the update was successful.
// If the session has a packet cached (common when first setting up a session), it will be sent. // If the session has a packet cached (common when first setting up a session), it will be sent.
func (ss *sessions) handlePing(ping *sessionPing) { func (ss *sessions) handlePing(ping *sessionPing) {
// Get the corresponding session (or create a new session) // Get the corresponding session (or create a new session)
sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub) sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub)
switch { switch {
case ping.IsPong: // This is a response, not an initial ping, so ignore it.
case isIn: // Session already exists case isIn: // Session already exists
case !ss.isSessionAllowed(&ping.SendPermPub, false): // Session is not allowed case !ss.isSessionAllowed(&ping.SendPermPub, false): // Session is not allowed
case ping.IsPong: // This is a response, not an initial ping, so ignore it.
default: default:
ss.listenerMutex.Lock() ss.listenerMutex.Lock()
if ss.listener != nil { if ss.listener != nil {
@ -383,23 +377,24 @@ func (ss *sessions) handlePing(ping *sessionPing) {
if s, _ := ss.getByTheirPerm(&ping.SendPermPub); s != sinfo { if s, _ := ss.getByTheirPerm(&ping.SendPermPub); s != sinfo {
panic("This should not happen") panic("This should not happen")
} }
conn := newConn(ss.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo) conn := newConn(ss.router.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo)
for i := range conn.nodeMask { for i := range conn.nodeMask {
conn.nodeMask[i] = 0xFF conn.nodeMask[i] = 0xFF
} }
sinfo.setConn(ss.router, conn)
c := ss.listener.conn c := ss.listener.conn
go func() { c <- conn }() go func() { c <- conn }()
} }
ss.listenerMutex.Unlock() ss.listenerMutex.Unlock()
} }
if sinfo != nil { if sinfo != nil {
sinfo.doFunc(func() { sinfo.Act(ss.router, func() {
// Update the session // Update the session
if !sinfo.update(ping) { /*panic("Should not happen in testing")*/ if !sinfo._update(ping) { /*panic("Should not happen in testing")*/
return return
} }
if !ping.IsPong { if !ping.IsPong {
ss.sendPingPong(sinfo, true) sinfo._sendPingPong(true)
} }
}) })
} }
@ -408,7 +403,7 @@ func (ss *sessions) handlePing(ping *sessionPing) {
// Get the MTU of the session. // Get the MTU of the session.
// Will be equal to the smaller of this node's MTU or the remote node's MTU. // Will be equal to the smaller of this node's MTU or the remote node's MTU.
// If sending over links with a maximum message size (this was a thing with the old UDP code), it could be further lowered, to a minimum of 1280. // If sending over links with a maximum message size (this was a thing with the old UDP code), it could be further lowered, to a minimum of 1280.
func (sinfo *sessionInfo) getMTU() uint16 { func (sinfo *sessionInfo) _getMTU() uint16 {
if sinfo.theirMTU == 0 || sinfo.myMTU == 0 { if sinfo.theirMTU == 0 || sinfo.myMTU == 0 {
return 0 return 0
} }
@ -419,7 +414,7 @@ func (sinfo *sessionInfo) getMTU() uint16 {
} }
// Checks if a packet's nonce is recent enough to fall within the window of allowed packets, and not already received. // Checks if a packet's nonce is recent enough to fall within the window of allowed packets, and not already received.
func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool { func (sinfo *sessionInfo) _nonceIsOK(theirNonce *crypto.BoxNonce) bool {
// The bitmask is to allow for some non-duplicate out-of-order packets // The bitmask is to allow for some non-duplicate out-of-order packets
if theirNonce.Minus(&sinfo.theirNonce) > 0 { if theirNonce.Minus(&sinfo.theirNonce) > 0 {
// This is newer than the newest nonce we've seen // This is newer than the newest nonce we've seen
@ -437,7 +432,7 @@ func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool {
} }
// Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce // Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce
func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) {
// Start with some cleanup // Start with some cleanup
for len(sinfo.theirNonceHeap) > 64 { for len(sinfo.theirNonceHeap) > 64 {
if time.Since(sinfo.theirNonceMap[*sinfo.theirNonceHeap.peek()]) < nonceWindow { if time.Since(sinfo.theirNonceMap[*sinfo.theirNonceHeap.peek()]) < nonceWindow {
@ -461,7 +456,7 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) {
// Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change.
func (ss *sessions) reset() { func (ss *sessions) reset() {
for _, sinfo := range ss.sinfos { for _, sinfo := range ss.sinfos {
sinfo.doFunc(func() { sinfo.Act(ss.router, func() {
sinfo.reset = true sinfo.reset = true
}) })
} }
@ -471,198 +466,119 @@ func (ss *sessions) reset() {
//////////////////////////// Worker Functions Below //////////////////////////// //////////////////////////// Worker Functions Below ////////////////////////////
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
func (sinfo *sessionInfo) startWorkers() { type sessionCryptoManager struct {
go sinfo.recvWorker() phony.Inbox
go sinfo.sendWorker()
} }
func (m *sessionCryptoManager) workerGo(from phony.Actor, f func()) {
m.Act(from, func() {
util.WorkerGo(f)
})
}
var manager = sessionCryptoManager{}
type FlowKeyMessage struct { type FlowKeyMessage struct {
FlowKey uint64 FlowKey uint64
Message []byte Message []byte
} }
func (sinfo *sessionInfo) recvWorker() { func (sinfo *sessionInfo) recv(from phony.Actor, packet *wire_trafficPacket) {
// TODO move theirNonce etc into a struct that gets stored here, passed in over a channel sinfo.Act(from, func() {
// Since there's no reason for anywhere else in the session code to need to *read* it... sinfo._recvPacket(packet)
// Only needs to be updated from the outside if a ping resets it... })
// That would get rid of the need to take a mutex for the sessionFunc
var callbacks []chan func()
doRecv := func(p wire_trafficPacket) {
var bs []byte
var err error
var k crypto.BoxSharedKey
sessionFunc := func() {
if !sinfo.nonceIsOK(&p.Nonce) {
err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0}
return
}
k = sinfo.sharedSesKey
}
sinfo.doFunc(sessionFunc)
if err != nil {
util.PutBytes(p.Payload)
return
}
var isOK bool
ch := make(chan func(), 1)
poolFunc := func() {
bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce)
callback := func() {
util.PutBytes(p.Payload)
if !isOK {
util.PutBytes(bs)
return
}
sessionFunc = func() {
if k != sinfo.sharedSesKey || !sinfo.nonceIsOK(&p.Nonce) {
// The session updated in the mean time, so return an error
err = ConnError{errors.New("session updated during crypto operation"), false, true, false, 0}
return
}
sinfo.updateNonce(&p.Nonce)
sinfo.time = time.Now()
sinfo.bytesRecvd += uint64(len(bs))
}
sinfo.doFunc(sessionFunc)
if err != nil {
// Not sure what else to do with this packet, I guess just drop it
util.PutBytes(bs)
} else {
// Pass the packet to the buffer for Conn.Read
select {
case <-sinfo.cancel.Finished():
util.PutBytes(bs)
case sinfo.recv <- bs:
}
}
}
ch <- callback
}
// Send to the worker and wait for it to finish
util.WorkerGo(poolFunc)
callbacks = append(callbacks, ch)
}
fromHelper := make(chan wire_trafficPacket, 1)
go func() {
var buf []wire_trafficPacket
for {
for len(buf) > 0 {
select {
case <-sinfo.cancel.Finished():
return
case p := <-sinfo.fromRouter:
buf = append(buf, p)
for len(buf) > 64 { // Based on nonce window size
util.PutBytes(buf[0].Payload)
buf = buf[1:]
}
case fromHelper <- buf[0]:
buf = buf[1:]
}
}
select {
case <-sinfo.cancel.Finished():
return
case p := <-sinfo.fromRouter:
buf = append(buf, p)
}
}
}()
select {
case <-sinfo.cancel.Finished():
return
case <-sinfo.init:
// Wait until the session has finished initializing before processing any packets
}
for {
for len(callbacks) > 0 {
select {
case f := <-callbacks[0]:
callbacks = callbacks[1:]
f()
case <-sinfo.cancel.Finished():
return
case p := <-fromHelper:
doRecv(p)
}
}
select {
case <-sinfo.cancel.Finished():
return
case p := <-fromHelper:
doRecv(p)
}
}
} }
func (sinfo *sessionInfo) sendWorker() { func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) {
// TODO move info that this worker needs here, send updates via a channel
// Otherwise we need to take a mutex to avoid races with update()
var callbacks []chan func()
doSend := func(msg FlowKeyMessage) {
var p wire_trafficPacket
var k crypto.BoxSharedKey
sessionFunc := func() {
sinfo.bytesSent += uint64(len(msg.Message))
p = wire_trafficPacket{
Coords: append([]byte(nil), sinfo.coords...),
Handle: sinfo.theirHandle,
Nonce: sinfo.myNonce,
}
if msg.FlowKey != 0 {
// Helps ensure that traffic from this flow ends up in a separate queue from other flows
// The zero padding relies on the fact that the self-peer is always on port 0
p.Coords = append(p.Coords, 0)
p.Coords = wire_put_uint64(msg.FlowKey, p.Coords)
}
sinfo.myNonce.Increment()
k = sinfo.sharedSesKey
}
// Get the mutex-protected info needed to encrypt the packet
sinfo.doFunc(sessionFunc)
ch := make(chan func(), 1)
poolFunc := func() {
// Encrypt the packet
p.Payload, _ = crypto.BoxSeal(&k, msg.Message, &p.Nonce)
// The callback will send the packet
callback := func() {
// Encoding may block on a util.GetBytes(), so kept out of the worker pool
packet := p.encode()
// Cleanup
util.PutBytes(msg.Message)
util.PutBytes(p.Payload)
// Send the packet
sinfo.core.router.out(packet)
}
ch <- callback
}
// Send to the worker and wait for it to finish
util.WorkerGo(poolFunc)
callbacks = append(callbacks, ch)
}
select { select {
case <-sinfo.cancel.Finished():
return
case <-sinfo.init: case <-sinfo.init:
// Wait until the session has finished initializing before processing any packets default:
// TODO find a better way to drop things until initialized
util.PutBytes(p.Payload)
return
} }
for { if !sinfo._nonceIsOK(&p.Nonce) {
for len(callbacks) > 0 { util.PutBytes(p.Payload)
select { return
case f := <-callbacks[0]: }
callbacks = callbacks[1:] k := sinfo.sharedSesKey
f() var isOK bool
case <-sinfo.cancel.Finished(): var bs []byte
ch := make(chan func(), 1)
poolFunc := func() {
bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce)
callback := func() {
util.PutBytes(p.Payload)
if !isOK || k != sinfo.sharedSesKey || !sinfo._nonceIsOK(&p.Nonce) {
// Either we failed to decrypt, or the session was updated, or we received this packet in the mean time
util.PutBytes(bs)
return return
case msg := <-sinfo.send: }
doSend(msg) sinfo._updateNonce(&p.Nonce)
sinfo.time = time.Now()
sinfo.bytesRecvd += uint64(len(bs))
sinfo.conn.recvMsg(sinfo, bs)
}
ch <- callback
sinfo.checkCallbacks()
}
sinfo.callbacks = append(sinfo.callbacks, ch)
manager.workerGo(sinfo, poolFunc)
}
func (sinfo *sessionInfo) _send(msg FlowKeyMessage) {
select {
case <-sinfo.init:
default:
// TODO find a better way to drop things until initialized
util.PutBytes(msg.Message)
return
}
sinfo.bytesSent += uint64(len(msg.Message))
coords := append([]byte(nil), sinfo.coords...)
if msg.FlowKey != 0 {
coords = append(coords, 0)
coords = append(coords, wire_encode_uint64(msg.FlowKey)...)
}
p := wire_trafficPacket{
Coords: coords,
Handle: sinfo.theirHandle,
Nonce: sinfo.myNonce,
}
sinfo.myNonce.Increment()
k := sinfo.sharedSesKey
ch := make(chan func(), 1)
poolFunc := func() {
p.Payload, _ = crypto.BoxSeal(&k, msg.Message, &p.Nonce)
callback := func() {
// Encoding may block on a util.GetBytes(), so kept out of the worker pool
packet := p.encode()
// Cleanup
util.PutBytes(msg.Message)
util.PutBytes(p.Payload)
// Send the packet
// TODO replace this with a send to the peer struct if that becomes an actor
sinfo.sessions.router.Act(sinfo, func() {
sinfo.sessions.router.out(packet)
})
}
ch <- callback
sinfo.checkCallbacks()
}
sinfo.callbacks = append(sinfo.callbacks, ch)
manager.workerGo(sinfo, poolFunc)
}
func (sinfo *sessionInfo) checkCallbacks() {
sinfo.Act(nil, func() {
if len(sinfo.callbacks) > 0 {
select {
case callback := <-sinfo.callbacks[0]:
sinfo.callbacks = sinfo.callbacks[1:]
callback()
sinfo.checkCallbacks()
default:
} }
} }
select { })
case <-sinfo.cancel.Finished():
return
case bs := <-sinfo.send:
doSend(bs)
}
}
} }

View File

@ -19,6 +19,8 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/Arceliar/phony"
) )
const ( const (
@ -162,22 +164,18 @@ type switchData struct {
// All the information stored by the switch. // All the information stored by the switch.
type switchTable struct { type switchTable struct {
core *Core core *Core
reconfigure chan chan error key crypto.SigPubKey // Our own key
key crypto.SigPubKey // Our own key time time.Time // Time when locator.tstamp was last updated
time time.Time // Time when locator.tstamp was last updated drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root
drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root mutex sync.RWMutex // Lock for reads/writes of switchData
mutex sync.RWMutex // Lock for reads/writes of switchData parent switchPort // Port of whatever peer is our parent, or self if we're root
parent switchPort // Port of whatever peer is our parent, or self if we're root data switchData //
data switchData // updater atomic.Value // *sync.Once
updater atomic.Value // *sync.Once table atomic.Value // lookupTable
table atomic.Value // lookupTable phony.Inbox // Owns the below
packetIn chan []byte // Incoming packets for the worker to handle queues switch_buffers // Queues - not atomic so ONLY use through the actor
idleIn chan switchPort // Incoming idle notifications from peer links idle map[switchPort]time.Time // idle peers - not atomic so ONLY use through the actor
admin chan func() // Pass a lambda for the admin socket to query stuff
queues switch_buffers // Queues - not atomic so ONLY use through admin chan
queueTotalMaxSize uint64 // Maximum combined size of queues
toRouter chan []byte // Packets to be sent to the router
} }
// Minimum allowed total size of switch queues. // Minimum allowed total size of switch queues.
@ -187,7 +185,6 @@ const SwitchQueueTotalMinSize = 4 * 1024 * 1024
func (t *switchTable) init(core *Core) { func (t *switchTable) init(core *Core) {
now := time.Now() now := time.Now()
t.core = core t.core = core
t.reconfigure = make(chan chan error, 1)
t.key = t.core.sigPub t.key = t.core.sigPub
locator := switchLocator{root: t.key, tstamp: now.Unix()} locator := switchLocator{root: t.key, tstamp: now.Unix()}
peers := make(map[switchPort]peerInfo) peers := make(map[switchPort]peerInfo)
@ -195,11 +192,23 @@ func (t *switchTable) init(core *Core) {
t.updater.Store(&sync.Once{}) t.updater.Store(&sync.Once{})
t.table.Store(lookupTable{}) t.table.Store(lookupTable{})
t.drop = make(map[crypto.SigPubKey]int64) t.drop = make(map[crypto.SigPubKey]int64)
t.packetIn = make(chan []byte, 1024) phony.Block(t, func() {
t.idleIn = make(chan switchPort, 1024) core.config.Mutex.RLock()
t.admin = make(chan func()) if core.config.Current.SwitchOptions.MaxTotalQueueSize > SwitchQueueTotalMinSize {
t.queueTotalMaxSize = SwitchQueueTotalMinSize t.queues.totalMaxSize = core.config.Current.SwitchOptions.MaxTotalQueueSize
t.toRouter = make(chan []byte, 1) } else {
t.queues.totalMaxSize = SwitchQueueTotalMinSize
}
core.config.Mutex.RUnlock()
t.queues.bufs = make(map[string]switch_buffer)
t.idle = make(map[switchPort]time.Time)
})
}
func (t *switchTable) reconfigure() {
// This is where reconfiguration would go, if we had anything useful to do.
t.core.link.reconfigure()
t.core.peers.reconfigure()
} }
// Safely gets a copy of this node's locator. // Safely gets a copy of this node's locator.
@ -245,13 +254,10 @@ func (t *switchTable) cleanRoot() {
if t.data.locator.root != t.key { if t.data.locator.root != t.key {
t.data.seq++ t.data.seq++
t.updater.Store(&sync.Once{}) t.updater.Store(&sync.Once{})
select { t.core.router.reset(nil)
case t.core.router.reset <- struct{}{}:
default:
}
} }
t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()}
t.core.peers.sendSwitchMsgs() t.core.peers.sendSwitchMsgs(t)
} }
} }
@ -279,7 +285,7 @@ func (t *switchTable) blockPeer(port switchPort) {
} }
// Removes a peer. // Removes a peer.
// Must be called by the router mainLoop goroutine, e.g. call router.doAdmin with a lambda that calls this. // Must be called by the router actor with a lambda that calls this.
// If the removed peer was this node's parent, it immediately tries to find a new parent. // If the removed peer was this node's parent, it immediately tries to find a new parent.
func (t *switchTable) forgetPeer(port switchPort) { func (t *switchTable) forgetPeer(port switchPort) {
t.mutex.Lock() t.mutex.Lock()
@ -511,17 +517,14 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep
if !equiv(&sender.locator, &t.data.locator) { if !equiv(&sender.locator, &t.data.locator) {
doUpdate = true doUpdate = true
t.data.seq++ t.data.seq++
select { t.core.router.reset(nil)
case t.core.router.reset <- struct{}{}:
default:
}
} }
if t.data.locator.tstamp != sender.locator.tstamp { if t.data.locator.tstamp != sender.locator.tstamp {
t.time = now t.time = now
} }
t.data.locator = sender.locator t.data.locator = sender.locator
t.parent = sender.port t.parent = sender.port
t.core.peers.sendSwitchMsgs() t.core.peers.sendSwitchMsgs(t)
} }
if doUpdate { if doUpdate {
t.updater.Store(&sync.Once{}) t.updater.Store(&sync.Once{})
@ -573,7 +576,7 @@ func (t *switchTable) getTable() lookupTable {
// Starts the switch worker // Starts the switch worker
func (t *switchTable) start() error { func (t *switchTable) start() error {
t.core.log.Infoln("Starting switch") t.core.log.Infoln("Starting switch")
go t.doWorker() // There's actually nothing to do to start it...
return nil return nil
} }
@ -659,12 +662,13 @@ func (t *switchTable) bestPortForCoords(coords []byte) switchPort {
// Handle an incoming packet // Handle an incoming packet
// Either send it to ourself, or to the first idle peer that's free // Either send it to ourself, or to the first idle peer that's free
// Returns true if the packet has been handled somehow, false if it should be queued // Returns true if the packet has been handled somehow, false if it should be queued
func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) bool { func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]time.Time) bool {
coords := switch_getPacketCoords(packet) coords := switch_getPacketCoords(packet)
closer := t.getCloser(coords) closer := t.getCloser(coords)
if len(closer) == 0 { if len(closer) == 0 {
// TODO? call the router directly, and remove the whole concept of a self peer? // TODO? call the router directly, and remove the whole concept of a self peer?
t.toRouter <- packet self := t.core.peers.getPorts()[0]
self.sendPacketsFrom(t, [][]byte{packet})
return true return true
} }
var best *peer var best *peer
@ -709,7 +713,7 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
if best != nil { if best != nil {
// Send to the best idle next hop // Send to the best idle next hop
delete(idle, best.port) delete(idle, best.port)
best.sendPackets([][]byte{packet}) best.sendPacketsFrom(t, [][]byte{packet})
return true return true
} }
// Didn't find anyone idle to send it to // Didn't find anyone idle to send it to
@ -729,15 +733,15 @@ type switch_buffer struct {
} }
type switch_buffers struct { type switch_buffers struct {
switchTable *switchTable totalMaxSize uint64
bufs map[string]switch_buffer // Buffers indexed by StreamID bufs map[string]switch_buffer // Buffers indexed by StreamID
size uint64 // Total size of all buffers, in bytes size uint64 // Total size of all buffers, in bytes
maxbufs int maxbufs int
maxsize uint64 maxsize uint64
closer []closerInfo // Scratch space closer []closerInfo // Scratch space
} }
func (b *switch_buffers) cleanup(t *switchTable) { func (b *switch_buffers) _cleanup(t *switchTable) {
for streamID, buf := range b.bufs { for streamID, buf := range b.bufs {
// Remove queues for which we have no next hop // Remove queues for which we have no next hop
packet := buf.packets[0] packet := buf.packets[0]
@ -751,7 +755,7 @@ func (b *switch_buffers) cleanup(t *switchTable) {
} }
} }
for b.size > b.switchTable.queueTotalMaxSize { for b.size > b.totalMaxSize {
// Drop a random queue // Drop a random queue
target := rand.Uint64() % b.size target := rand.Uint64() % b.size
var size uint64 // running total var size uint64 // running total
@ -779,14 +783,14 @@ func (b *switch_buffers) cleanup(t *switchTable) {
// Handles incoming idle notifications // Handles incoming idle notifications
// Loops over packets and sends the newest one that's OK for this peer to send // Loops over packets and sends the newest one that's OK for this peer to send
// Returns true if the peer is no longer idle, false if it should be added to the idle list // Returns true if the peer is no longer idle, false if it should be added to the idle list
func (t *switchTable) handleIdle(port switchPort) bool { func (t *switchTable) _handleIdle(port switchPort) bool {
to := t.core.peers.getPorts()[port] to := t.core.peers.getPorts()[port]
if to == nil { if to == nil {
return true return true
} }
var packets [][]byte var packets [][]byte
var psize int var psize int
t.queues.cleanup(t) t.queues._cleanup(t)
now := time.Now() now := time.Now()
for psize < 65535 { for psize < 65535 {
var best string var best string
@ -823,102 +827,49 @@ func (t *switchTable) handleIdle(port switchPort) bool {
} }
} }
if len(packets) > 0 { if len(packets) > 0 {
to.sendPackets(packets) to.sendPacketsFrom(t, packets)
return true return true
} }
return false return false
} }
// The switch worker does routing lookups and sends packets to where they need to be func (t *switchTable) packetInFrom(from phony.Actor, bytes []byte) {
func (t *switchTable) doWorker() { t.Act(from, func() {
sendingToRouter := make(chan []byte, 1) t._packetIn(bytes)
go func() { })
// Keep sending packets to the router }
self := t.core.peers.getPorts()[0]
for bs := range sendingToRouter { func (t *switchTable) _packetIn(bytes []byte) {
self.sendPackets([][]byte{bs}) // Try to send it somewhere (or drop it if it's corrupt or at a dead end)
if !t._handleIn(bytes, t.idle) {
// There's nobody free to take it right now, so queue it for later
packet := switch_packetInfo{bytes, time.Now()}
streamID := switch_getPacketStreamID(packet.bytes)
buf, bufExists := t.queues.bufs[streamID]
buf.packets = append(buf.packets, packet)
buf.size += uint64(len(packet.bytes))
t.queues.size += uint64(len(packet.bytes))
// Keep a track of the max total queue size
if t.queues.size > t.queues.maxsize {
t.queues.maxsize = t.queues.size
} }
}() t.queues.bufs[streamID] = buf
go func() { if !bufExists {
// Keep taking packets from the idle worker and sending them to the above whenever it's idle, keeping anything extra in a (fifo, head-drop) buffer // Keep a track of the max total queue count. Only recalculate this
var buf [][]byte // when the queue is new because otherwise repeating len(dict) might
var size int // cause unnecessary processing overhead
for { if len(t.queues.bufs) > t.queues.maxbufs {
bs := <-t.toRouter t.queues.maxbufs = len(t.queues.bufs)
size += len(bs)
buf = append(buf, bs)
for len(buf) > 0 {
select {
case bs := <-t.toRouter:
size += len(bs)
buf = append(buf, bs)
for size > int(t.queueTotalMaxSize) {
size -= len(buf[0])
util.PutBytes(buf[0])
buf = buf[1:]
}
case sendingToRouter <- buf[0]:
size -= len(buf[0])
buf = buf[1:]
}
} }
} }
}() t.queues._cleanup(t)
t.queues.switchTable = t
t.queues.bufs = make(map[string]switch_buffer) // Packets per PacketStreamID (string)
idle := make(map[switchPort]time.Time) // this is to deduplicate things
for {
//t.core.log.Debugf("Switch state: idle = %d, buffers = %d", len(idle), len(t.queues.bufs))
select {
case bytes := <-t.packetIn:
// Try to send it somewhere (or drop it if it's corrupt or at a dead end)
if !t.handleIn(bytes, idle) {
// There's nobody free to take it right now, so queue it for later
packet := switch_packetInfo{bytes, time.Now()}
streamID := switch_getPacketStreamID(packet.bytes)
buf, bufExists := t.queues.bufs[streamID]
buf.packets = append(buf.packets, packet)
buf.size += uint64(len(packet.bytes))
t.queues.size += uint64(len(packet.bytes))
// Keep a track of the max total queue size
if t.queues.size > t.queues.maxsize {
t.queues.maxsize = t.queues.size
}
t.queues.bufs[streamID] = buf
if !bufExists {
// Keep a track of the max total queue count. Only recalculate this
// when the queue is new because otherwise repeating len(dict) might
// cause unnecessary processing overhead
if len(t.queues.bufs) > t.queues.maxbufs {
t.queues.maxbufs = len(t.queues.bufs)
}
}
t.queues.cleanup(t)
}
case port := <-t.idleIn:
// Try to find something to send to this peer
if !t.handleIdle(port) {
// Didn't find anything ready to send yet, so stay idle
idle[port] = time.Now()
}
case f := <-t.admin:
f()
case e := <-t.reconfigure:
e <- nil
}
} }
} }
// Passed a function to call. func (t *switchTable) _idleIn(port switchPort) {
// This will send the function to t.admin and block until it finishes. // Try to find something to send to this peer
func (t *switchTable) doAdmin(f func()) { if !t._handleIdle(port) {
// Pass this a function that needs to be run by the router's main goroutine // Didn't find anything ready to send yet, so stay idle
// It will pass the function to the router and wait for the router to finish t.idle[port] = time.Now()
done := make(chan struct{})
newF := func() {
f()
close(done)
} }
t.admin <- newF
<-done
} }

View File

@ -33,12 +33,11 @@ const tcp_ping_interval = (default_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 tcp struct { type tcp struct {
link *link link *link
reconfigure chan chan error mutex sync.Mutex // Protecting the below
mutex sync.Mutex // Protecting the below listeners map[string]*TcpListener
listeners map[string]*TcpListener calls map[string]struct{}
calls map[string]struct{} conns map[linkInfo](chan struct{})
conns map[linkInfo](chan struct{})
} }
// TcpListener is a stoppable TCP listener interface. These are typically // TcpListener is a stoppable TCP listener interface. These are typically
@ -76,49 +75,12 @@ func (t *tcp) getAddr() *net.TCPAddr {
// Initializes the struct. // Initializes the struct.
func (t *tcp) init(l *link) error { func (t *tcp) init(l *link) error {
t.link = l t.link = l
t.reconfigure = make(chan chan error, 1)
t.mutex.Lock() t.mutex.Lock()
t.calls = make(map[string]struct{}) t.calls = make(map[string]struct{})
t.conns = make(map[linkInfo](chan struct{})) t.conns = make(map[linkInfo](chan struct{}))
t.listeners = make(map[string]*TcpListener) t.listeners = make(map[string]*TcpListener)
t.mutex.Unlock() t.mutex.Unlock()
go func() {
for {
e := <-t.reconfigure
t.link.core.config.Mutex.RLock()
added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen)
deleted := util.Difference(t.link.core.config.Previous.Listen, t.link.core.config.Current.Listen)
t.link.core.config.Mutex.RUnlock()
if len(added) > 0 || len(deleted) > 0 {
for _, a := range added {
if a[:6] != "tcp://" {
continue
}
if _, err := t.listen(a[6:]); err != nil {
e <- err
continue
}
}
for _, d := range deleted {
if d[:6] != "tcp://" {
continue
}
t.mutex.Lock()
if listener, ok := t.listeners[d[6:]]; ok {
t.mutex.Unlock()
listener.Stop <- true
} else {
t.mutex.Unlock()
}
}
e <- nil
} else {
e <- nil
}
}
}()
t.link.core.config.Mutex.RLock() t.link.core.config.Mutex.RLock()
defer t.link.core.config.Mutex.RUnlock() defer t.link.core.config.Mutex.RUnlock()
for _, listenaddr := range t.link.core.config.Current.Listen { for _, listenaddr := range t.link.core.config.Current.Listen {
@ -133,6 +95,38 @@ func (t *tcp) init(l *link) error {
return nil return nil
} }
func (t *tcp) reconfigure() {
t.link.core.config.Mutex.RLock()
added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen)
deleted := util.Difference(t.link.core.config.Previous.Listen, t.link.core.config.Current.Listen)
t.link.core.config.Mutex.RUnlock()
if len(added) > 0 || len(deleted) > 0 {
for _, a := range added {
if a[:6] != "tcp://" {
continue
}
if _, err := t.listen(a[6:]); err != nil {
t.link.core.log.Errorln("Error adding TCP", a[6:], "listener:", err)
} else {
t.link.core.log.Infoln("Started TCP listener:", a[6:])
}
}
for _, d := range deleted {
if d[:6] != "tcp://" {
continue
}
t.mutex.Lock()
if listener, ok := t.listeners[d[6:]]; ok {
t.mutex.Unlock()
listener.Stop <- true
t.link.core.log.Infoln("Stopped TCP listener:", d[6:])
} else {
t.mutex.Unlock()
}
}
}
}
func (t *tcp) listen(listenaddr string) (*TcpListener, error) { func (t *tcp) listen(listenaddr string) (*TcpListener, error) {
var err error var err error