yggdrasil-go/src/yggdrasil/session.go

441 lines
12 KiB
Go
Raw Normal View History

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
import "time"
type sessionInfo struct {
2018-01-04 22:37:51 +00:00
core *Core
theirAddr address
theirSubnet subnet
theirPermPub boxPubKey
theirSesPub boxPubKey
mySesPub boxPubKey
mySesPriv boxPrivKey
sharedSesKey boxSharedKey // derived from session keys
theirHandle handle
myHandle handle
theirNonce boxNonce
myNonce boxNonce
theirMTU uint16
myMTU uint16
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
tstamp int64 // tstamp from their last session ping, replay attack mitigation
mtuTime time.Time // time myMTU was last changed
pingTime time.Time // time the first ping was sent since the last received packet
pingSend time.Time // time the last ping was sent
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
}
type sessionPing struct {
2018-01-04 22:37:51 +00:00
sendPermPub boxPubKey // Sender's permanent key
handle handle // Random number to ID session
sendSesPub boxPubKey // Session key to use
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
}
// Returns true if the session was updated, false otherwise
func (s *sessionInfo) update(p *sessionPing) bool {
2018-01-04 22:37:51 +00:00
if !(p.tstamp > s.tstamp) {
// To protect against replay attacks
2018-01-04 22:37:51 +00:00
return false
}
if p.sendPermPub != s.theirPermPub {
// 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-04 22:37:51 +00:00
if p.sendSesPub != s.theirSesPub {
s.theirSesPub = p.sendSesPub
s.theirHandle = p.handle
s.sharedSesKey = *getSharedKey(&s.mySesPriv, &s.theirSesPub)
s.theirNonce = boxNonce{}
s.nonceMask = 0
}
if p.mtu >= 1280 || p.mtu == 0 {
s.theirMTU = p.mtu
}
2018-01-04 22:37:51 +00:00
s.coords = append([]byte{}, p.coords...)
now := time.Now()
s.time = now
2018-01-04 22:37:51 +00:00
s.tstamp = p.tstamp
s.init = true
return true
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
}
type sessions struct {
2018-01-04 22:37:51 +00:00
core *Core
// Maps known permanent keys to their shared key, used by DHT a lot
permShared map[boxPubKey]*boxSharedKey
// Maps (secret) handle onto session info
sinfos map[handle]*sessionInfo
// Maps mySesPub onto handle
byMySes map[boxPubKey]*handle
// Maps theirPermPub onto handle
byTheirPerm map[boxPubKey]*handle
addrToPerm map[address]*boxPubKey
subnetToPerm map[subnet]*boxPubKey
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
ss.permShared = make(map[boxPubKey]*boxSharedKey)
ss.sinfos = make(map[handle]*sessionInfo)
ss.byMySes = make(map[boxPubKey]*handle)
ss.byTheirPerm = make(map[boxPubKey]*handle)
ss.addrToPerm = make(map[address]*boxPubKey)
ss.subnetToPerm = make(map[subnet]*boxPubKey)
2017-12-29 04:16:20 +00:00
}
func (ss *sessions) getSessionForHandle(handle *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
}
func (ss *sessions) getByMySes(key *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
}
func (ss *sessions) getByTheirPerm(key *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
}
func (ss *sessions) getByTheirAddr(addr *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
}
func (ss *sessions) getByTheirSubnet(snet *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
}
func (ss *sessions) createSession(theirPermKey *boxPubKey) *sessionInfo {
2018-01-04 22:37:51 +00:00
sinfo := sessionInfo{}
sinfo.core = ss.core
sinfo.theirPermPub = *theirPermKey
pub, priv := newBoxKeys()
sinfo.mySesPub = *pub
sinfo.mySesPriv = *priv
sinfo.myNonce = *newBoxNonce()
sinfo.theirMTU = 1280
sinfo.myMTU = uint16(ss.core.tun.mtu)
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
}
sinfo.myHandle = *newHandle()
sinfo.theirAddr = *address_addrForNodeID(getNodeID(&sinfo.theirPermPub))
sinfo.theirSubnet = *address_subnetForNodeID(getNodeID(&sinfo.theirPermPub))
sinfo.send = make(chan []byte, 32)
sinfo.recv = make(chan *wire_trafficPacket, 32)
2018-01-04 22:37:51 +00:00
go sinfo.doWorker()
// Do some cleanup
// Time thresholds almost certainly could use some adjusting
for _, s := range ss.sinfos {
if s.timedout() {
s.close()
}
}
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
}
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
}
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{
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
}
sinfo.myNonce.update()
return ref
2017-12-29 04:16:20 +00:00
}
func (ss *sessions) getSharedKey(myPriv *boxPrivKey,
2018-01-04 22:37:51 +00:00
theirPub *boxPubKey) *boxSharedKey {
if skey, isIn := ss.permShared[*theirPub]; isIn {
return skey
}
// First do some cleanup
const maxKeys = dht_bucket_number * dht_bucket_size
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)
}
ss.permShared[*theirPub] = getSharedKey(myPriv, theirPub)
return ss.permShared[*theirPub]
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
}
func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) {
2018-01-04 22:37:51 +00:00
ping := ss.getPing(sinfo)
ping.isPong = isPong
bs := ping.encode()
shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub)
payload, nonce := boxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{
2018-06-02 20:21:05 +00:00
TTL: ^uint64(0),
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)
if !isPong {
sinfo.pingSend = time.Now()
}
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)
sinfo, isIn := ss.getByTheirPerm(&ping.sendPermPub)
if !isIn || sinfo.timedout() {
if isIn {
sinfo.close()
}
ss.createSession(&ping.sendPermPub)
sinfo, isIn = ss.getByTheirPerm(&ping.sendPermPub)
if !isIn {
panic("This should not happen")
}
}
// Update the session
if !sinfo.update(ping) { /*panic("Should not happen in testing")*/
return
}
if !ping.isPong {
ss.sendPingPong(sinfo, true)
}
if sinfo.packet != nil {
// send
var bs []byte
bs, sinfo.packet = sinfo.packet, nil
ss.core.router.sendPacket(bs)
2018-01-04 22:37:51 +00:00
}
2017-12-29 04:16:20 +00:00
}
func (n *boxNonce) minus(m *boxNonce) int64 {
2018-01-04 22:37:51 +00:00
diff := int64(0)
for idx := range n {
diff *= 256
diff += int64(n[idx]) - int64(m[idx])
if diff > 64 {
diff = 64
}
if diff < -64 {
diff = -64
}
}
return diff
2017-12-29 04:16:20 +00:00
}
func (sinfo *sessionInfo) getMTU() uint16 {
if sinfo.theirMTU == 0 || sinfo.myMTU == 0 {
return 0
}
if sinfo.theirMTU < sinfo.myMTU {
return sinfo.theirMTU
}
return sinfo.myMTU
}
2017-12-29 04:16:20 +00:00
func (sinfo *sessionInfo) nonceIsOK(theirNonce *boxNonce) bool {
2018-01-04 22:37:51 +00:00
// The bitmask is to allow for some non-duplicate out-of-order packets
diff := theirNonce.minus(&sinfo.theirNonce)
if diff > 0 {
return true
}
return ^sinfo.nonceMask&(0x01<<uint64(-diff)) != 0
2017-12-29 04:16:20 +00:00
}
func (sinfo *sessionInfo) updateNonce(theirNonce *boxNonce) {
2018-01-04 22:37:51 +00:00
// Shift nonce mask if needed
// Set bit
diff := theirNonce.minus(&sinfo.theirNonce)
if diff > 0 {
sinfo.nonceMask <<= uint64(diff)
sinfo.nonceMask &= 0x01
} else {
sinfo.nonceMask &= 0x01 << uint64(-diff)
}
sinfo.theirNonce = *theirNonce
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
}
////////////////////////////////////////////////////////////////////////////////
// This is for a per-session worker
// It handles calling the relatively expensive crypto operations
// It's also responsible for keeping nonces consistent
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
}
}
}
2017-12-29 04:16:20 +00:00
}
func (sinfo *sessionInfo) doSend(bs []byte) {
2018-01-04 22:37:51 +00:00
defer util_putBytes(bs)
if !sinfo.init {
return
} // To prevent using empty session keys
payload, nonce := boxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce)
defer util_putBytes(payload)
p := wire_trafficPacket{
2018-06-02 20:21:05 +00:00
TTL: ^uint64(0),
Coords: sinfo.coords,
Handle: sinfo.theirHandle,
Nonce: *nonce,
Payload: payload,
2018-01-04 22:37:51 +00:00
}
packet := p.encode()
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
}
func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) {
2018-06-02 20:21:05 +00:00
defer util_putBytes(p.Payload)
payloadSize := uint16(len(p.Payload))
if !sinfo.nonceIsOK(&p.Nonce) {
2018-01-04 22:37:51 +00:00
return
}
2018-06-02 20:21:05 +00:00
bs, isOK := boxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce)
2018-01-04 22:37:51 +00:00
if !isOK {
// We're going to guess that the session MTU is too large
// Set myMTU to the largest value we think we can receive
fixSessionMTU := func() {
// This clamps down to 1280 almost immediately over ipv4
// Over link-local ipv6, it seems to approach link MTU
// So maybe it's doing the right thing?...
//sinfo.core.log.Println("DEBUG got bad packet:", payloadSize)
newMTU := payloadSize - boxOverhead
if newMTU < 1280 {
newMTU = 1280
}
if newMTU < sinfo.myMTU {
sinfo.myMTU = newMTU
//sinfo.core.log.Println("DEBUG set MTU to:", sinfo.myMTU)
sinfo.core.sessions.sendPingPong(sinfo, false)
sinfo.mtuTime = time.Now()
sinfo.wasMTUFixed = true
}
}
go func() { sinfo.core.router.admin <- fixSessionMTU }()
2018-01-04 22:37:51 +00:00
util_putBytes(bs)
return
}
fixSessionMTU := func() {
if time.Since(sinfo.mtuTime) > time.Minute {
sinfo.myMTU = uint16(sinfo.core.tun.mtu)
sinfo.mtuTime = time.Now()
//sinfo.core.log.Println("DEBUG: Reset MTU to:", sinfo.myMTU)
}
}
go func() { sinfo.core.router.admin <- fixSessionMTU }()
2018-06-02 20:21:05 +00:00
sinfo.updateNonce(&p.Nonce)
2018-01-04 22:37:51 +00:00
sinfo.time = time.Now()
sinfo.bytesRecvd += uint64(len(bs))
sinfo.core.router.recvPacket(bs, &sinfo.theirAddr, &sinfo.theirSubnet)
2017-12-29 04:16:20 +00:00
}