mirror of
				https://github.com/yggdrasil-network/yggdrasil-go.git
				synced 2025-11-04 11:15:07 +03:00 
			
		
		
		
	keep track of recent nonces with a heap and a map instead of a fixed-size bitmask
This commit is contained in:
		
							parent
							
								
									fdac8932a8
								
							
						
					
					
						commit
						03b8af9f1a
					
				
					 1 changed files with 83 additions and 48 deletions
				
			
		| 
						 | 
				
			
			@ -6,6 +6,7 @@ package yggdrasil
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"container/heap"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
| 
						 | 
				
			
			@ -15,42 +16,62 @@ import (
 | 
			
		|||
	"github.com/yggdrasil-network/yggdrasil-go/src/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Duration that we keep track of old nonces per session, to allow some out-of-order packet delivery
 | 
			
		||||
const nonceWindow = time.Second
 | 
			
		||||
 | 
			
		||||
// A heap of nonces, used with a map[nonce]time to allow out-of-order packets a little time to arrive without rejecting them
 | 
			
		||||
// Less is backwards so the oldest node is the highest priority for Pop
 | 
			
		||||
type nonceHeap []crypto.BoxNonce
 | 
			
		||||
 | 
			
		||||
func (h nonceHeap) Len() int            { return len(h) }
 | 
			
		||||
func (h nonceHeap) Less(i, j int) bool  { return h[i].Minus(&h[j]) > 0 }
 | 
			
		||||
func (h nonceHeap) Swap(i, j int)       { h[i], h[j] = h[j], h[i] }
 | 
			
		||||
func (h *nonceHeap) Push(x interface{}) { *h = append(*h, x.(crypto.BoxNonce)) }
 | 
			
		||||
func (h *nonceHeap) Pop() interface{} {
 | 
			
		||||
	l := len(*h)
 | 
			
		||||
	var n crypto.BoxNonce
 | 
			
		||||
	n, *h = (*h)[l-1], (*h)[:l-1]
 | 
			
		||||
	return n
 | 
			
		||||
}
 | 
			
		||||
func (h nonceHeap) peek() *crypto.BoxNonce { return &h[len(h)-1] }
 | 
			
		||||
 | 
			
		||||
// 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.
 | 
			
		||||
type sessionInfo struct {
 | 
			
		||||
	mutex          sync.Mutex              // Protects all of the below, use it any time you read/chance the contents of a session
 | 
			
		||||
	core           *Core                   //
 | 
			
		||||
	reconfigure    chan chan error         //
 | 
			
		||||
	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         //
 | 
			
		||||
	theirNonceMask uint64                  //
 | 
			
		||||
	myNonce        crypto.BoxNonce         //
 | 
			
		||||
	theirMTU       uint16                  //
 | 
			
		||||
	myMTU          uint16                  //
 | 
			
		||||
	wasMTUFixed    bool                    // Was the MTU fixed by a receive error?
 | 
			
		||||
	timeOpened     time.Time               // Time the sessino was opened
 | 
			
		||||
	time           time.Time               // Time we last received a packet
 | 
			
		||||
	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
 | 
			
		||||
	coords         []byte                  // coords of destination
 | 
			
		||||
	reset          bool                    // reset if coords change
 | 
			
		||||
	tstamp         int64                   // ATOMIC - tstamp from their last session ping, replay attack mitigation
 | 
			
		||||
	bytesSent      uint64                  // Bytes of real traffic sent 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
 | 
			
		||||
	cancel         util.Cancellation       // Used to terminate workers
 | 
			
		||||
	fromRouter     chan wire_trafficPacket // Received packets go here, to be decrypted by the session
 | 
			
		||||
	recv           chan []byte             // Decrypted packets go here, picked up by the associated Conn
 | 
			
		||||
	send           chan FlowKeyMessage     // Packets with optional flow key go here, to be encrypted and sent
 | 
			
		||||
	mutex          sync.Mutex                    // Protects all of the below, use it any time you read/chance the contents of a session
 | 
			
		||||
	core           *Core                         //
 | 
			
		||||
	reconfigure    chan chan error               //
 | 
			
		||||
	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               //
 | 
			
		||||
	theirNonceHeap nonceHeap                     // priority queue to keep track of the lowest nonce we recently accepted
 | 
			
		||||
	theirNonceMap  map[crypto.BoxNonce]time.Time // time we added each nonce to the heap
 | 
			
		||||
	myNonce        crypto.BoxNonce               //
 | 
			
		||||
	theirMTU       uint16                        //
 | 
			
		||||
	myMTU          uint16                        //
 | 
			
		||||
	wasMTUFixed    bool                          // Was the MTU fixed by a receive error?
 | 
			
		||||
	timeOpened     time.Time                     // Time the sessino was opened
 | 
			
		||||
	time           time.Time                     // Time we last received a packet
 | 
			
		||||
	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
 | 
			
		||||
	coords         []byte                        // coords of destination
 | 
			
		||||
	reset          bool                          // reset if coords change
 | 
			
		||||
	tstamp         int64                         // ATOMIC - tstamp from their last session ping, replay attack mitigation
 | 
			
		||||
	bytesSent      uint64                        // Bytes of real traffic sent 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
 | 
			
		||||
	cancel         util.Cancellation             // Used to terminate workers
 | 
			
		||||
	fromRouter     chan wire_trafficPacket       // Received packets go here, to be decrypted by the session
 | 
			
		||||
	recv           chan []byte                   // Decrypted packets go here, picked up by the associated Conn
 | 
			
		||||
	send           chan FlowKeyMessage           // Packets with optional flow key go here, to be encrypted and sent
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sinfo *sessionInfo) doFunc(f func()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +108,8 @@ func (s *sessionInfo) update(p *sessionPing) bool {
 | 
			
		|||
		s.theirHandle = p.Handle
 | 
			
		||||
		s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub)
 | 
			
		||||
		s.theirNonce = crypto.BoxNonce{}
 | 
			
		||||
		s.theirNonceMask = 0
 | 
			
		||||
		s.theirNonceHeap = nil
 | 
			
		||||
		s.theirNonceMap = make(map[crypto.BoxNonce]time.Time)
 | 
			
		||||
	}
 | 
			
		||||
	if p.MTU >= 1280 || p.MTU == 0 {
 | 
			
		||||
		s.theirMTU = p.MTU
 | 
			
		||||
| 
						 | 
				
			
			@ -400,27 +422,40 @@ 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.
 | 
			
		||||
func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool {
 | 
			
		||||
	// The bitmask is to allow for some non-duplicate out-of-order packets
 | 
			
		||||
	diff := theirNonce.Minus(&sinfo.theirNonce)
 | 
			
		||||
	if diff > 0 {
 | 
			
		||||
	if theirNonce.Minus(&sinfo.theirNonce) > 0 {
 | 
			
		||||
		// This is newer than the newest nonce we've seen
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return ^sinfo.theirNonceMask&(0x01<<uint64(-diff)) != 0
 | 
			
		||||
	if len(sinfo.theirNonceHeap) > 0 {
 | 
			
		||||
		if theirNonce.Minus(sinfo.theirNonceHeap.peek()) > 0 {
 | 
			
		||||
			if _, isIn := sinfo.theirNonceMap[*theirNonce]; !isIn {
 | 
			
		||||
				// This nonce is recent enough that we keep track of older nonces, but it's not one we've seen yet
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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) {
 | 
			
		||||
	// Shift nonce mask if needed
 | 
			
		||||
	// Set bit
 | 
			
		||||
	diff := theirNonce.Minus(&sinfo.theirNonce)
 | 
			
		||||
	if diff > 0 {
 | 
			
		||||
		// This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info.
 | 
			
		||||
		sinfo.theirNonceMask <<= uint64(diff)
 | 
			
		||||
		sinfo.theirNonceMask &= 0x01
 | 
			
		||||
		sinfo.theirNonce = *theirNonce
 | 
			
		||||
	} else {
 | 
			
		||||
		// This nonce is older, so set the bit but do not shift the window.
 | 
			
		||||
		sinfo.theirNonceMask &= 0x01 << uint64(-diff)
 | 
			
		||||
	// Start with some cleanup
 | 
			
		||||
	for len(sinfo.theirNonceHeap) > 64 {
 | 
			
		||||
		if time.Since(sinfo.theirNonceMap[*sinfo.theirNonceHeap.peek()]) < nonceWindow {
 | 
			
		||||
			// This nonce is still fairly new, so keep it around
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		// TODO? reallocate the map in some cases, to free unused map space?
 | 
			
		||||
		delete(sinfo.theirNonceMap, *sinfo.theirNonceHeap.peek())
 | 
			
		||||
		heap.Pop(&sinfo.theirNonceHeap)
 | 
			
		||||
	}
 | 
			
		||||
	if theirNonce.Minus(&sinfo.theirNonce) > 0 {
 | 
			
		||||
		// This nonce is the newest we've seen, so make a note of that
 | 
			
		||||
		sinfo.theirNonce = *theirNonce
 | 
			
		||||
	}
 | 
			
		||||
	// Add it to the heap/map so we know not to allow it again
 | 
			
		||||
	heap.Push(&sinfo.theirNonceHeap, *theirNonce)
 | 
			
		||||
	sinfo.theirNonceMap[*theirNonce] = time.Now()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Resets all sessions to an uninitialized state.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue