2017-12-29 04:16:20 +00:00
package yggdrasil
// This is the session manager
// It's responsible for keeping track of open sessions to other nodes
// The session information consists of crypto keys and coords
2018-07-30 13:44:46 +00:00
import (
"bytes"
2018-10-07 16:13:41 +00:00
"encoding/hex"
2018-07-30 13:44:46 +00:00
"time"
2018-12-15 02:49:18 +00:00
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
2018-07-30 13:44:46 +00:00
)
2017-12-29 04:16:20 +00:00
2018-06-10 23:03:28 +00:00
// 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.
2017-12-29 04:16:20 +00:00
type sessionInfo struct {
2018-01-04 22:37:51 +00:00
core * Core
2018-12-30 12:04:42 +00:00
reconfigure chan chan error
2018-12-15 02:49:18 +00:00
theirAddr address . Address
theirSubnet address . Subnet
theirPermPub crypto . BoxPubKey
theirSesPub crypto . BoxPubKey
mySesPub crypto . BoxPubKey
mySesPriv crypto . BoxPrivKey
sharedSesKey crypto . BoxSharedKey // derived from session keys
theirHandle crypto . Handle
myHandle crypto . Handle
theirNonce crypto . BoxNonce
myNonce crypto . BoxNonce
2018-02-11 23:09:05 +00:00
theirMTU uint16
myMTU uint16
2018-05-18 16:59:29 +00:00
wasMTUFixed bool // Was the MTU fixed by a receive error?
2018-01-04 22:37:51 +00:00
time time . Time // Time we last received a packet
coords [ ] byte // coords of destination
packet [ ] byte // a buffered packet, sent immediately on ping/pong
init bool // Reset if coords change
send chan [ ] byte
recv chan * wire_trafficPacket
nonceMask uint64
2018-02-26 02:24:36 +00:00
tstamp int64 // tstamp from their last session ping, replay attack mitigation
mtuTime time . Time // time myMTU was last changed
2018-04-22 20:31:30 +00:00
pingTime time . Time // time the first ping was sent since the last received packet
pingSend time . Time // time the last ping was sent
2018-05-18 16:59:29 +00:00
bytesSent uint64 // Bytes of real traffic sent in this session
bytesRecvd uint64 // Bytes of real traffic received in this session
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// 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.
2017-12-29 04:16:20 +00:00
type sessionPing struct {
2018-12-15 02:49:18 +00:00
SendPermPub crypto . BoxPubKey // Sender's permanent key
Handle crypto . Handle // Random number to ID session
SendSesPub crypto . BoxPubKey // Session key to use
2018-06-02 21:19:42 +00:00
Coords [ ] byte
Tstamp int64 // unix time, but the only real requirement is that it increases
IsPong bool
MTU uint16
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// 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.
2017-12-29 04:16:20 +00:00
func ( s * sessionInfo ) update ( p * sessionPing ) bool {
2018-06-02 21:19:42 +00:00
if ! ( p . Tstamp > s . tstamp ) {
2018-01-26 23:30:51 +00:00
// To protect against replay attacks
2018-01-04 22:37:51 +00:00
return false
}
2018-06-02 21:19:42 +00:00
if p . SendPermPub != s . theirPermPub {
2018-01-26 23:30:51 +00:00
// Should only happen if two sessions got the same handle
// That shouldn't be allowed anyway, but if it happens then let one time out
2018-01-04 22:37:51 +00:00
return false
2018-01-26 23:30:51 +00:00
}
2018-06-02 21:19:42 +00:00
if p . SendSesPub != s . theirSesPub {
s . theirSesPub = p . SendSesPub
s . theirHandle = p . Handle
2018-12-15 02:49:18 +00:00
s . sharedSesKey = * crypto . GetSharedKey ( & s . mySesPriv , & s . theirSesPub )
s . theirNonce = crypto . BoxNonce { }
2018-01-04 22:37:51 +00:00
s . nonceMask = 0
}
2018-06-02 21:19:42 +00:00
if p . MTU >= 1280 || p . MTU == 0 {
s . theirMTU = p . MTU
2018-02-11 23:09:05 +00:00
}
2018-07-30 13:44:46 +00:00
if ! bytes . Equal ( s . coords , p . Coords ) {
// allocate enough space for additional coords
s . coords = append ( make ( [ ] byte , 0 , len ( p . Coords ) + 11 ) , p . Coords ... )
}
2018-04-22 20:31:30 +00:00
now := time . Now ( )
s . time = now
2018-06-02 21:19:42 +00:00
s . tstamp = p . Tstamp
2018-01-04 22:37:51 +00:00
s . init = true
return true
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Returns true if the session has been idle for longer than the allowed timeout.
2017-12-29 04:16:20 +00:00
func ( s * sessionInfo ) timedout ( ) bool {
2018-01-04 22:37:51 +00:00
return time . Since ( s . time ) > time . Minute
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Struct of all active sessions.
// Sessions are indexed by handle.
// Additionally, stores maps of address/subnet onto keys, and keys onto handles.
2017-12-29 04:16:20 +00:00
type sessions struct {
2018-06-22 01:31:30 +00:00
core * Core
2018-12-30 12:04:42 +00:00
reconfigure chan chan error
2018-06-22 01:31:30 +00:00
lastCleanup time . Time
2018-01-04 22:37:51 +00:00
// Maps known permanent keys to their shared key, used by DHT a lot
2018-12-15 02:49:18 +00:00
permShared map [ crypto . BoxPubKey ] * crypto . BoxSharedKey
2018-01-04 22:37:51 +00:00
// Maps (secret) handle onto session info
2018-12-15 02:49:18 +00:00
sinfos map [ crypto . Handle ] * sessionInfo
2018-01-04 22:37:51 +00:00
// Maps mySesPub onto handle
2018-12-15 02:49:18 +00:00
byMySes map [ crypto . BoxPubKey ] * crypto . Handle
2018-01-04 22:37:51 +00:00
// Maps theirPermPub onto handle
2018-12-15 02:49:18 +00:00
byTheirPerm map [ crypto . BoxPubKey ] * crypto . Handle
addrToPerm map [ address . Address ] * crypto . BoxPubKey
subnetToPerm map [ address . Subnet ] * crypto . BoxPubKey
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Initializes the session struct.
2017-12-29 04:16:20 +00:00
func ( ss * sessions ) init ( core * Core ) {
2018-01-04 22:37:51 +00:00
ss . core = core
2018-12-30 12:04:42 +00:00
ss . reconfigure = make ( chan chan error , 1 )
2018-12-29 18:51:51 +00:00
go func ( ) {
for {
2019-01-15 08:51:19 +00:00
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
2018-12-29 18:51:51 +00:00
}
}
2019-01-15 08:51:19 +00:00
e <- nil
2018-12-29 18:51:51 +00:00
}
} ( )
2018-12-15 02:49:18 +00:00
ss . permShared = make ( map [ crypto . BoxPubKey ] * crypto . BoxSharedKey )
ss . sinfos = make ( map [ crypto . Handle ] * sessionInfo )
ss . byMySes = make ( map [ crypto . BoxPubKey ] * crypto . Handle )
ss . byTheirPerm = make ( map [ crypto . BoxPubKey ] * crypto . Handle )
ss . addrToPerm = make ( map [ address . Address ] * crypto . BoxPubKey )
ss . subnetToPerm = make ( map [ address . Subnet ] * crypto . BoxPubKey )
2018-06-22 01:31:30 +00:00
ss . lastCleanup = time . Now ( )
2017-12-29 04:16:20 +00:00
}
2019-01-16 20:38:51 +00:00
// Determines whether the session firewall is enabled.
func ( ss * sessions ) isSessionFirewallEnabled ( ) bool {
ss . core . configMutex . RLock ( )
defer ss . core . configMutex . RUnlock ( )
2018-10-07 16:13:41 +00:00
2019-01-16 20:38:51 +00:00
return ss . core . config . SessionFirewall . Enable
2018-10-07 16:13:41 +00:00
}
// Determines whether the session with a given publickey is allowed based on
// session firewall rules.
2018-12-15 02:49:18 +00:00
func ( ss * sessions ) isSessionAllowed ( pubkey * crypto . BoxPubKey , initiator bool ) bool {
2019-01-16 20:38:51 +00:00
ss . core . configMutex . RLock ( )
defer ss . core . configMutex . RUnlock ( )
2019-01-14 18:24:35 +00:00
2018-10-07 16:13:41 +00:00
// Allow by default if the session firewall is disabled
2019-01-16 20:38:51 +00:00
if ! ss . isSessionFirewallEnabled ( ) {
2018-10-07 16:13:41 +00:00
return true
}
// Prepare for checking whitelist/blacklist
2018-12-15 02:49:18 +00:00
var box crypto . BoxPubKey
2018-10-07 16:13:41 +00:00
// Reject blacklisted nodes
2019-01-16 20:38:51 +00:00
for _ , b := range ss . core . config . SessionFirewall . BlacklistEncryptionPublicKeys {
2018-10-07 16:13:41 +00:00
key , err := hex . DecodeString ( b )
if err == nil {
2018-12-15 02:49:18 +00:00
copy ( box [ : crypto . BoxPubKeyLen ] , key )
2018-10-07 16:13:41 +00:00
if box == * pubkey {
return false
}
}
}
// Allow whitelisted nodes
2019-01-16 20:38:51 +00:00
for _ , b := range ss . core . config . SessionFirewall . WhitelistEncryptionPublicKeys {
2018-10-07 16:13:41 +00:00
key , err := hex . DecodeString ( b )
if err == nil {
2018-12-15 02:49:18 +00:00
copy ( box [ : crypto . BoxPubKeyLen ] , key )
2018-10-07 16:13:41 +00:00
if box == * pubkey {
return true
}
}
}
2018-10-08 18:51:51 +00:00
// Allow outbound sessions if appropriate
2019-01-16 20:38:51 +00:00
if ss . core . config . SessionFirewall . AlwaysAllowOutbound {
2018-10-08 18:51:51 +00:00
if initiator {
return true
}
}
2018-10-07 16:13:41 +00:00
// Look and see if the pubkey is that of a direct peer
var isDirectPeer bool
for _ , peer := range ss . core . peers . ports . Load ( ) . ( map [ switchPort ] * peer ) {
if peer . box == * pubkey {
isDirectPeer = true
break
}
}
// Allow direct peers if appropriate
2019-01-16 20:38:51 +00:00
if ss . core . config . SessionFirewall . AllowFromDirect && isDirectPeer {
2018-10-07 16:13:41 +00:00
return true
}
// Allow remote nodes if appropriate
2019-01-16 20:38:51 +00:00
if ss . core . config . SessionFirewall . AllowFromRemote && ! isDirectPeer {
2018-10-07 16:13:41 +00:00
return true
}
// Finally, default-deny if not matching any of the above rules
return false
}
2018-06-10 23:03:28 +00:00
// Gets the session corresponding to a given handle.
2018-12-15 02:49:18 +00:00
func ( ss * sessions ) getSessionForHandle ( handle * crypto . Handle ) ( * sessionInfo , bool ) {
2018-01-04 22:37:51 +00:00
sinfo , isIn := ss . sinfos [ * handle ]
if isIn && sinfo . timedout ( ) {
// We have a session, but it has timed out
return nil , false
}
return sinfo , isIn
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Gets a session corresponding to an ephemeral session key used by this node.
2018-12-15 02:49:18 +00:00
func ( ss * sessions ) getByMySes ( key * crypto . BoxPubKey ) ( * sessionInfo , bool ) {
2018-01-04 22:37:51 +00:00
h , isIn := ss . byMySes [ * key ]
if ! isIn {
return nil , false
}
sinfo , isIn := ss . getSessionForHandle ( h )
return sinfo , isIn
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Gets a session corresponding to a permanent key used by the remote node.
2018-12-15 02:49:18 +00:00
func ( ss * sessions ) getByTheirPerm ( key * crypto . BoxPubKey ) ( * sessionInfo , bool ) {
2018-01-04 22:37:51 +00:00
h , isIn := ss . byTheirPerm [ * key ]
if ! isIn {
return nil , false
}
sinfo , isIn := ss . getSessionForHandle ( h )
return sinfo , isIn
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Gets a session corresponding to an IPv6 address used by the remote node.
2018-12-15 02:49:18 +00:00
func ( ss * sessions ) getByTheirAddr ( addr * address . Address ) ( * sessionInfo , bool ) {
2018-01-04 22:37:51 +00:00
p , isIn := ss . addrToPerm [ * addr ]
if ! isIn {
return nil , false
}
sinfo , isIn := ss . getByTheirPerm ( p )
return sinfo , isIn
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Gets a session corresponding to an IPv6 /64 subnet used by the remote node/network.
2018-12-15 02:49:18 +00:00
func ( ss * sessions ) getByTheirSubnet ( snet * address . Subnet ) ( * sessionInfo , bool ) {
2018-01-04 22:37:51 +00:00
p , isIn := ss . subnetToPerm [ * snet ]
if ! isIn {
return nil , false
}
sinfo , isIn := ss . getByTheirPerm ( p )
return sinfo , isIn
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Creates a new session and lazily cleans up old/timedout existing sessions.
// This includse initializing session info to sane defaults (e.g. lowest supported MTU).
2018-12-15 02:49:18 +00:00
func ( ss * sessions ) createSession ( theirPermKey * crypto . BoxPubKey ) * sessionInfo {
2019-01-14 18:24:35 +00:00
if ! ss . isSessionAllowed ( theirPermKey , true ) {
return nil
2018-10-08 18:51:51 +00:00
}
2018-01-04 22:37:51 +00:00
sinfo := sessionInfo { }
sinfo . core = ss . core
2018-12-30 12:04:42 +00:00
sinfo . reconfigure = make ( chan chan error , 1 )
2018-01-04 22:37:51 +00:00
sinfo . theirPermPub = * theirPermKey
2018-12-15 02:49:18 +00:00
pub , priv := crypto . NewBoxKeys ( )
2018-01-04 22:37:51 +00:00
sinfo . mySesPub = * pub
sinfo . mySesPriv = * priv
2018-12-15 02:49:18 +00:00
sinfo . myNonce = * crypto . NewBoxNonce ( )
2018-02-11 23:09:05 +00:00
sinfo . theirMTU = 1280
2018-12-14 17:35:02 +00:00
sinfo . myMTU = uint16 ( ss . core . router . tun . mtu )
2018-04-22 20:31:30 +00:00
now := time . Now ( )
sinfo . time = now
sinfo . mtuTime = now
sinfo . pingTime = now
sinfo . pingSend = now
2018-01-04 22:37:51 +00:00
higher := false
for idx := range ss . core . boxPub {
if ss . core . boxPub [ idx ] > sinfo . theirPermPub [ idx ] {
higher = true
break
} else if ss . core . boxPub [ idx ] < sinfo . theirPermPub [ idx ] {
break
}
}
if higher {
// higher => odd nonce
sinfo . myNonce [ len ( sinfo . myNonce ) - 1 ] |= 0x01
} else {
// lower => even nonce
sinfo . myNonce [ len ( sinfo . myNonce ) - 1 ] &= 0xfe
}
2018-12-15 02:49:18 +00:00
sinfo . myHandle = * crypto . NewHandle ( )
sinfo . theirAddr = * address . AddrForNodeID ( crypto . GetNodeID ( & sinfo . theirPermPub ) )
sinfo . theirSubnet = * address . SubnetForNodeID ( crypto . GetNodeID ( & sinfo . theirPermPub ) )
2018-02-04 00:44:28 +00:00
sinfo . send = make ( chan [ ] byte , 32 )
sinfo . recv = make ( chan * wire_trafficPacket , 32 )
2018-01-04 22:37:51 +00:00
go sinfo . doWorker ( )
ss . sinfos [ sinfo . myHandle ] = & sinfo
ss . byMySes [ sinfo . mySesPub ] = & sinfo . myHandle
ss . byTheirPerm [ sinfo . theirPermPub ] = & sinfo . myHandle
ss . addrToPerm [ sinfo . theirAddr ] = & sinfo . theirPermPub
ss . subnetToPerm [ sinfo . theirSubnet ] = & sinfo . theirPermPub
return & sinfo
2017-12-29 04:16:20 +00:00
}
2018-06-22 01:31:30 +00:00
func ( ss * sessions ) cleanup ( ) {
// Time thresholds almost certainly could use some adjusting
2018-11-25 18:25:38 +00:00
for k := range ss . permShared {
// Delete a key, to make sure this eventually shrinks to 0
delete ( ss . permShared , k )
break
}
2018-06-22 01:31:30 +00:00
if time . Since ( ss . lastCleanup ) < time . Minute {
return
}
for _ , s := range ss . sinfos {
if s . timedout ( ) {
s . close ( )
}
}
2018-12-15 02:49:18 +00:00
permShared := make ( map [ crypto . BoxPubKey ] * crypto . BoxSharedKey , len ( ss . permShared ) )
2018-11-25 18:25:38 +00:00
for k , v := range ss . permShared {
permShared [ k ] = v
}
ss . permShared = permShared
2018-12-15 02:49:18 +00:00
sinfos := make ( map [ crypto . Handle ] * sessionInfo , len ( ss . sinfos ) )
2018-11-25 18:25:38 +00:00
for k , v := range ss . sinfos {
sinfos [ k ] = v
}
ss . sinfos = sinfos
2018-12-15 02:49:18 +00:00
byMySes := make ( map [ crypto . BoxPubKey ] * crypto . Handle , len ( ss . byMySes ) )
2018-11-25 18:25:38 +00:00
for k , v := range ss . byMySes {
byMySes [ k ] = v
}
ss . byMySes = byMySes
2018-12-15 02:49:18 +00:00
byTheirPerm := make ( map [ crypto . BoxPubKey ] * crypto . Handle , len ( ss . byTheirPerm ) )
2018-11-25 18:25:38 +00:00
for k , v := range ss . byTheirPerm {
byTheirPerm [ k ] = v
}
ss . byTheirPerm = byTheirPerm
2018-12-15 02:49:18 +00:00
addrToPerm := make ( map [ address . Address ] * crypto . BoxPubKey , len ( ss . addrToPerm ) )
2018-11-25 18:25:38 +00:00
for k , v := range ss . addrToPerm {
addrToPerm [ k ] = v
}
ss . addrToPerm = addrToPerm
2018-12-15 02:49:18 +00:00
subnetToPerm := make ( map [ address . Subnet ] * crypto . BoxPubKey , len ( ss . subnetToPerm ) )
2018-11-25 18:25:38 +00:00
for k , v := range ss . subnetToPerm {
subnetToPerm [ k ] = v
}
ss . subnetToPerm = subnetToPerm
2018-06-22 01:31:30 +00:00
ss . lastCleanup = time . Now ( )
}
2018-06-10 23:03:28 +00:00
// Closes a session, removing it from sessions maps and killing the worker goroutine.
2017-12-29 04:16:20 +00:00
func ( sinfo * sessionInfo ) close ( ) {
2018-01-04 22:37:51 +00:00
delete ( sinfo . core . sessions . sinfos , sinfo . myHandle )
delete ( sinfo . core . sessions . byMySes , sinfo . mySesPub )
delete ( sinfo . core . sessions . byTheirPerm , sinfo . theirPermPub )
delete ( sinfo . core . sessions . addrToPerm , sinfo . theirAddr )
delete ( sinfo . core . sessions . subnetToPerm , sinfo . theirSubnet )
close ( sinfo . send )
close ( sinfo . recv )
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Returns a session ping appropriate for the given session info.
2017-12-29 04:16:20 +00:00
func ( ss * sessions ) getPing ( sinfo * sessionInfo ) sessionPing {
2018-01-04 22:37:51 +00:00
loc := ss . core . switchTable . getLocator ( )
coords := loc . getCoords ( )
ref := sessionPing {
2018-06-02 21:19:42 +00:00
SendPermPub : ss . core . boxPub ,
Handle : sinfo . myHandle ,
SendSesPub : sinfo . mySesPub ,
Tstamp : time . Now ( ) . Unix ( ) ,
Coords : coords ,
MTU : sinfo . myMTU ,
2018-01-04 22:37:51 +00:00
}
2018-12-15 02:49:18 +00:00
sinfo . myNonce . Increment ( )
2018-01-04 22:37:51 +00:00
return ref
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Gets the shared key for a pair of box keys.
// Used to cache recently used shared keys for protocol traffic.
// This comes up with dht req/res and session ping/pong traffic.
2018-12-15 02:49:18 +00:00
func ( ss * sessions ) getSharedKey ( myPriv * crypto . BoxPrivKey ,
theirPub * crypto . BoxPubKey ) * crypto . BoxSharedKey {
2018-01-04 22:37:51 +00:00
if skey , isIn := ss . permShared [ * theirPub ] ; isIn {
return skey
}
// First do some cleanup
2018-10-20 19:48:07 +00:00
const maxKeys = 1024
2018-01-04 22:37:51 +00:00
for key := range ss . permShared {
// Remove a random key until the store is small enough
if len ( ss . permShared ) < maxKeys {
break
}
delete ( ss . permShared , key )
}
2018-12-15 02:49:18 +00:00
ss . permShared [ * theirPub ] = crypto . GetSharedKey ( myPriv , theirPub )
2018-01-04 22:37:51 +00:00
return ss . permShared [ * theirPub ]
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Sends a session ping by calling sendPingPong in ping mode.
2017-12-29 04:16:20 +00:00
func ( ss * sessions ) ping ( sinfo * sessionInfo ) {
2018-01-04 22:37:51 +00:00
ss . sendPingPong ( sinfo , false )
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// 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.
2017-12-29 04:16:20 +00:00
func ( ss * sessions ) sendPingPong ( sinfo * sessionInfo , isPong bool ) {
2018-01-04 22:37:51 +00:00
ping := ss . getPing ( sinfo )
2018-06-02 21:19:42 +00:00
ping . IsPong = isPong
2018-01-04 22:37:51 +00:00
bs := ping . encode ( )
shared := ss . getSharedKey ( & ss . core . boxPriv , & sinfo . theirPermPub )
2018-12-15 02:49:18 +00:00
payload , nonce := crypto . BoxSeal ( shared , bs , nil )
2018-01-04 22:37:51 +00:00
p := wire_protoTrafficPacket {
2018-06-02 20:21:05 +00:00
Coords : sinfo . coords ,
ToKey : sinfo . theirPermPub ,
FromKey : ss . core . boxPub ,
Nonce : * nonce ,
Payload : payload ,
2018-01-04 22:37:51 +00:00
}
packet := p . encode ( )
ss . core . router . out ( packet )
2018-04-22 20:31:30 +00:00
if ! isPong {
sinfo . pingSend = time . Now ( )
}
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// 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.
2017-12-29 04:16:20 +00:00
func ( ss * sessions ) handlePing ( ping * sessionPing ) {
2018-01-04 22:37:51 +00:00
// Get the corresponding session (or create a new session)
2018-06-02 21:19:42 +00:00
sinfo , isIn := ss . getByTheirPerm ( & ping . SendPermPub )
2018-10-07 16:13:41 +00:00
// Check the session firewall
2019-01-16 20:38:51 +00:00
if ! isIn && ss . isSessionFirewallEnabled ( ) {
2018-10-08 18:51:51 +00:00
if ! ss . isSessionAllowed ( & ping . SendPermPub , false ) {
2018-10-07 16:13:41 +00:00
return
}
}
2018-01-04 22:37:51 +00:00
if ! isIn || sinfo . timedout ( ) {
if isIn {
sinfo . close ( )
}
2018-06-02 21:19:42 +00:00
ss . createSession ( & ping . SendPermPub )
sinfo , isIn = ss . getByTheirPerm ( & ping . SendPermPub )
2018-01-04 22:37:51 +00:00
if ! isIn {
panic ( "This should not happen" )
}
}
// Update the session
if ! sinfo . update ( ping ) { /*panic("Should not happen in testing")*/
return
}
2018-06-02 21:19:42 +00:00
if ! ping . IsPong {
2018-01-04 22:37:51 +00:00
ss . sendPingPong ( sinfo , true )
}
if sinfo . packet != nil {
// send
var bs [ ] byte
bs , sinfo . packet = sinfo . packet , nil
2018-02-15 18:02:08 +00:00
ss . core . router . sendPacket ( bs )
2018-01-04 22:37:51 +00:00
}
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Get the MTU of the session.
// 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.
2018-02-11 23:09:05 +00:00
func ( sinfo * sessionInfo ) getMTU ( ) uint16 {
2018-05-18 17:56:33 +00:00
if sinfo . theirMTU == 0 || sinfo . myMTU == 0 {
return 0
}
2018-02-11 23:09:05 +00:00
if sinfo . theirMTU < sinfo . myMTU {
return sinfo . theirMTU
}
return sinfo . myMTU
}
2018-06-10 23:03:28 +00:00
// Checks if a packet's nonce is recent enough to fall within the window of allowed packets, and not already received.
2018-12-15 02:49:18 +00:00
func ( sinfo * sessionInfo ) nonceIsOK ( theirNonce * crypto . BoxNonce ) bool {
2018-01-04 22:37:51 +00:00
// The bitmask is to allow for some non-duplicate out-of-order packets
2018-12-15 02:49:18 +00:00
diff := theirNonce . Minus ( & sinfo . theirNonce )
2018-01-04 22:37:51 +00:00
if diff > 0 {
return true
}
return ^ sinfo . nonceMask & ( 0x01 << uint64 ( - diff ) ) != 0
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// 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
2018-12-15 02:49:18 +00:00
func ( sinfo * sessionInfo ) updateNonce ( theirNonce * crypto . BoxNonce ) {
2018-01-04 22:37:51 +00:00
// Shift nonce mask if needed
// Set bit
2018-12-15 02:49:18 +00:00
diff := theirNonce . Minus ( & sinfo . theirNonce )
2018-01-04 22:37:51 +00:00
if diff > 0 {
2018-06-10 23:03:28 +00:00
// This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info.
2018-01-04 22:37:51 +00:00
sinfo . nonceMask <<= uint64 ( diff )
sinfo . nonceMask &= 0x01
2018-06-10 23:03:28 +00:00
sinfo . theirNonce = * theirNonce
2018-01-04 22:37:51 +00:00
} else {
2018-06-10 23:03:28 +00:00
// This nonce is older, so set the bit but do not shift the window.
2018-01-04 22:37:51 +00:00
sinfo . nonceMask &= 0x01 << uint64 ( - diff )
}
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// Resets all sessions to an uninitialized state.
// Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change.
2017-12-29 04:16:20 +00:00
func ( ss * sessions ) resetInits ( ) {
2018-01-04 22:37:51 +00:00
for _ , sinfo := range ss . sinfos {
sinfo . init = false
}
2017-12-29 04:16:20 +00:00
}
////////////////////////////////////////////////////////////////////////////////
2018-06-10 23:03:28 +00:00
// This is for a per-session worker.
// It handles calling the relatively expensive crypto operations.
// It's also responsible for checking nonces and dropping out-of-date/duplicate packets, or else calling the function to update nonces if the packet is OK.
2017-12-29 04:16:20 +00:00
func ( sinfo * sessionInfo ) doWorker ( ) {
2018-01-04 22:37:51 +00:00
for {
select {
case p , ok := <- sinfo . recv :
if ok {
sinfo . doRecv ( p )
} else {
return
}
case bs , ok := <- sinfo . send :
if ok {
sinfo . doSend ( bs )
} else {
return
}
2018-12-30 12:04:42 +00:00
case e := <- sinfo . reconfigure :
e <- nil
2018-01-04 22:37:51 +00:00
}
}
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// This encrypts a packet, creates a trafficPacket struct, encodes it, and sends it to router.out to pass it to the switch layer.
2017-12-29 04:16:20 +00:00
func ( sinfo * sessionInfo ) doSend ( bs [ ] byte ) {
2018-12-15 02:49:18 +00:00
defer util . PutBytes ( bs )
2018-01-04 22:37:51 +00:00
if ! sinfo . init {
2018-07-30 10:46:44 +00:00
// To prevent using empty session keys
2018-01-04 22:37:51 +00:00
return
2018-07-30 10:46:44 +00:00
}
2018-07-30 12:43:34 +00:00
// code isn't multithreaded so appending to this is safe
coords := sinfo . coords
2019-01-21 16:22:49 +00:00
// Work out the flowkey - this is used to determine which switch queue
// traffic will be pushed to in the event of congestion
2019-01-21 12:27:29 +00:00
var flowkey uint64
2019-01-21 16:22:49 +00:00
switch bs [ 0 ] & 0xf0 {
case 0x40 : // IPv4 packet
// Check the packet meets minimum UDP packet length
if len ( bs ) >= 24 {
if bs [ 9 ] == 0x06 || bs [ 9 ] == 0x11 || bs [ 9 ] == 0x84 {
ihl := bs [ 0 ] & 0x0f * 4 // Header length
flowkey = uint64 ( bs [ 9 ] ) << 32 /* proto */ |
uint64 ( bs [ ihl + 0 ] ) << 24 | uint64 ( bs [ ihl + 1 ] ) << 16 /* sport */ |
uint64 ( bs [ ihl + 2 ] ) << 8 | uint64 ( bs [ ihl + 3 ] ) /* dport */
2019-01-21 12:27:29 +00:00
}
2019-01-21 16:22:49 +00:00
}
case 0x60 : // IPv6 packet
// Check if the flowlabel was specified in the packet header
flowkey = uint64 ( bs [ 1 ] & 0x0f ) << 16 | uint64 ( bs [ 2 ] ) << 8 | uint64 ( bs [ 3 ] )
// If the flowlabel isn't present, make protokey from proto | sport | dport
// if the packet meets minimum UDP packet length
if flowkey == 0 && len ( bs ) >= 48 {
// Is the protocol TCP, UDP or SCTP?
if bs [ 6 ] == 0x06 || bs [ 6 ] == 0x11 || bs [ 6 ] == 0x84 {
flowkey = uint64 ( bs [ 6 ] ) << 32 /* proto */ |
uint64 ( bs [ 40 ] ) << 24 | uint64 ( bs [ 41 ] ) << 16 /* sport */ |
uint64 ( bs [ 42 ] ) << 8 | uint64 ( bs [ 43 ] ) /* dport */
2018-07-30 10:46:44 +00:00
}
}
}
// If we have a flowkey, either through the IPv6 flowlabel field or through
// known TCP/UDP/SCTP proto-sport-dport triplet, then append it to the coords.
// Appending extra coords after a 0 ensures that we still target the local router
// but lets us send extra data (which is otherwise ignored) to help separate
// traffic streams into independent queues
2018-07-30 02:15:57 +00:00
if flowkey != 0 {
2018-07-30 10:46:44 +00:00
coords = append ( coords , 0 ) // First target the local switchport
2018-07-30 02:15:57 +00:00
coords = wire_put_uint64 ( flowkey , coords ) // Then variable-length encoded flowkey
2018-07-22 11:00:40 +00:00
}
2018-07-30 10:46:44 +00:00
// Prepare the payload
2018-12-15 02:49:18 +00:00
payload , nonce := crypto . BoxSeal ( & sinfo . sharedSesKey , bs , & sinfo . myNonce )
defer util . PutBytes ( payload )
2018-01-04 22:37:51 +00:00
p := wire_trafficPacket {
2018-07-21 23:59:29 +00:00
Coords : coords ,
2018-06-02 20:21:05 +00:00
Handle : sinfo . theirHandle ,
Nonce : * nonce ,
Payload : payload ,
2018-01-04 22:37:51 +00:00
}
packet := p . encode ( )
2018-05-18 16:59:29 +00:00
sinfo . bytesSent += uint64 ( len ( bs ) )
2018-01-04 22:37:51 +00:00
sinfo . core . router . out ( packet )
2017-12-29 04:16:20 +00:00
}
2018-06-10 23:03:28 +00:00
// This takes a trafficPacket and checks the nonce.
// If the nonce is OK, it decrypts the packet.
// If the decrypted packet is OK, it calls router.recvPacket to pass the packet to the tun/tap.
// If a packet does not decrypt successfully, it assumes the packet was truncated, and updates the MTU accordingly.
// TODO? remove the MTU updating part? That should never happen with TCP peers, and the old UDP code that caused it was removed (and if replaced, should be replaced with something that can reliably send messages with an arbitrary size).
2017-12-29 04:16:20 +00:00
func ( sinfo * sessionInfo ) doRecv ( p * wire_trafficPacket ) {
2018-12-15 02:49:18 +00:00
defer util . PutBytes ( p . Payload )
2018-06-02 20:21:05 +00:00
if ! sinfo . nonceIsOK ( & p . Nonce ) {
2018-01-04 22:37:51 +00:00
return
}
2018-12-15 02:49:18 +00:00
bs , isOK := crypto . BoxOpen ( & sinfo . sharedSesKey , p . Payload , & p . Nonce )
2018-01-04 22:37:51 +00:00
if ! isOK {
2018-12-15 02:49:18 +00:00
util . PutBytes ( bs )
2018-01-04 22:37:51 +00:00
return
}
2018-06-02 20:21:05 +00:00
sinfo . updateNonce ( & p . Nonce )
2018-01-04 22:37:51 +00:00
sinfo . time = time . Now ( )
2018-05-18 16:59:29 +00:00
sinfo . bytesRecvd += uint64 ( len ( bs ) )
2018-11-21 04:04:18 +00:00
sinfo . core . router . toRecv <- router_recvPacket { bs , sinfo }
2017-12-29 04:16:20 +00:00
}