Improved timer state machine
This commit is contained in:
parent
5c1ccbddf0
commit
4ad62aaa6a
|
@ -16,6 +16,10 @@ const (
|
||||||
MaxHandshakeAttemptTime = time.Second * 90
|
MaxHandshakeAttemptTime = time.Second * 90
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RekeyAfterTimeReceiving = RekeyAfterTime - KeepaliveTimeout - RekeyTimeout
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
QueueOutboundSize = 1024
|
QueueOutboundSize = 1024
|
||||||
QueueInboundSize = 1024
|
QueueInboundSize = 1024
|
||||||
|
|
|
@ -31,16 +31,11 @@ type Device struct {
|
||||||
signal struct {
|
signal struct {
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
}
|
}
|
||||||
congestionState int32 // used as an atomic bool
|
underLoad int32 // used as an atomic bool
|
||||||
peers map[NoisePublicKey]*Peer
|
peers map[NoisePublicKey]*Peer
|
||||||
mac MACStateDevice
|
mac MACStateDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
CongestionStateUnderLoad = iota
|
|
||||||
CongestionStateOkay
|
|
||||||
)
|
|
||||||
|
|
||||||
func (device *Device) SetPrivateKey(sk NoisePrivateKey) {
|
func (device *Device) SetPrivateKey(sk NoisePrivateKey) {
|
||||||
device.mutex.Lock()
|
device.mutex.Lock()
|
||||||
defer device.mutex.Unlock()
|
defer device.mutex.Unlock()
|
||||||
|
@ -99,10 +94,12 @@ func NewDevice(tun TUNDevice, logLevel int) *Device {
|
||||||
go device.RoutineDecryption()
|
go device.RoutineDecryption()
|
||||||
go device.RoutineHandshake()
|
go device.RoutineHandshake()
|
||||||
}
|
}
|
||||||
|
|
||||||
go device.RoutineBusyMonitor()
|
go device.RoutineBusyMonitor()
|
||||||
go device.RoutineReadFromTUN(tun)
|
go device.RoutineReadFromTUN(tun)
|
||||||
go device.RoutineReceiveIncomming()
|
go device.RoutineReceiveIncomming()
|
||||||
go device.RoutineWriteToTUN(tun)
|
go device.RoutineWriteToTUN(tun)
|
||||||
|
|
||||||
return device
|
return device
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
153
src/handshake.go
153
src/handshake.go
|
@ -1,153 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* Sends a keep-alive if no packets queued for peer
|
|
||||||
*
|
|
||||||
* Used by initiator of handshake and with active keep-alive
|
|
||||||
*/
|
|
||||||
func (peer *Peer) SendKeepAlive() bool {
|
|
||||||
elem := peer.device.NewOutboundElement()
|
|
||||||
elem.packet = nil
|
|
||||||
if len(peer.queue.nonce) == 0 {
|
|
||||||
select {
|
|
||||||
case peer.queue.nonce <- elem:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called when a new authenticated message has been send
|
|
||||||
*
|
|
||||||
* TODO: This might be done in a faster way
|
|
||||||
*/
|
|
||||||
func (peer *Peer) KeepKeyFreshSending() {
|
|
||||||
send := func() bool {
|
|
||||||
peer.keyPairs.mutex.RLock()
|
|
||||||
defer peer.keyPairs.mutex.RUnlock()
|
|
||||||
|
|
||||||
kp := peer.keyPairs.current
|
|
||||||
if kp == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !kp.isInitiator {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
nonce := atomic.LoadUint64(&kp.sendNonce)
|
|
||||||
if nonce > RekeyAfterMessages {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return time.Now().Sub(kp.created) > RekeyAfterTime
|
|
||||||
}()
|
|
||||||
if send {
|
|
||||||
sendSignal(peer.signal.handshakeBegin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is the state machine for handshake initiation
|
|
||||||
*
|
|
||||||
* Associated with this routine is the signal "handshakeBegin"
|
|
||||||
* The routine will read from the "handshakeBegin" channel
|
|
||||||
* at most every RekeyTimeout seconds
|
|
||||||
*/
|
|
||||||
func (peer *Peer) RoutineHandshakeInitiator() {
|
|
||||||
device := peer.device
|
|
||||||
logger := device.log.Debug
|
|
||||||
timeout := stoppedTimer()
|
|
||||||
|
|
||||||
var elem *QueueOutboundElement
|
|
||||||
|
|
||||||
logger.Println("Routine, handshake initator, started for peer", peer.id)
|
|
||||||
|
|
||||||
func() {
|
|
||||||
for {
|
|
||||||
var attempts uint
|
|
||||||
var deadline time.Time
|
|
||||||
|
|
||||||
// wait for signal
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-peer.signal.handshakeBegin:
|
|
||||||
case <-peer.signal.stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
HandshakeLoop:
|
|
||||||
for {
|
|
||||||
// clear completed signal
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-peer.signal.handshakeCompleted:
|
|
||||||
case <-peer.signal.stop:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// create initiation
|
|
||||||
|
|
||||||
if elem != nil {
|
|
||||||
elem.Drop()
|
|
||||||
}
|
|
||||||
elem = device.NewOutboundElement()
|
|
||||||
|
|
||||||
msg, err := device.CreateMessageInitiation(peer)
|
|
||||||
if err != nil {
|
|
||||||
device.log.Error.Println("Failed to create initiation message:", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal & schedule for sending
|
|
||||||
|
|
||||||
writer := bytes.NewBuffer(elem.data[:0])
|
|
||||||
binary.Write(writer, binary.LittleEndian, msg)
|
|
||||||
elem.packet = writer.Bytes()
|
|
||||||
peer.mac.AddMacs(elem.packet)
|
|
||||||
addToOutboundQueue(peer.queue.outbound, elem)
|
|
||||||
|
|
||||||
if attempts == 0 {
|
|
||||||
deadline = time.Now().Add(MaxHandshakeAttemptTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set timeout
|
|
||||||
|
|
||||||
attempts += 1
|
|
||||||
stopTimer(timeout)
|
|
||||||
timeout.Reset(RekeyTimeout)
|
|
||||||
device.log.Debug.Println("Handshake initiation attempt", attempts, "queued for peer", peer.id)
|
|
||||||
|
|
||||||
// wait for handshake or timeout
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-peer.signal.stop:
|
|
||||||
return
|
|
||||||
|
|
||||||
case <-peer.signal.handshakeCompleted:
|
|
||||||
device.log.Debug.Println("Handshake complete")
|
|
||||||
break HandshakeLoop
|
|
||||||
|
|
||||||
case <-timeout.C:
|
|
||||||
device.log.Debug.Println("Timeout")
|
|
||||||
if deadline.Before(time.Now().Add(RekeyTimeout)) {
|
|
||||||
peer.signal.flushNonceQueue <- struct{}{}
|
|
||||||
if !peer.timer.sendKeepalive.Stop() {
|
|
||||||
<-peer.timer.sendKeepalive.C
|
|
||||||
}
|
|
||||||
break HandshakeLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
logger.Println("Routine, handshake initator, stopped for peer", peer.id)
|
|
||||||
}
|
|
|
@ -23,19 +23,6 @@ type KeyPairs struct {
|
||||||
next *KeyPair // not yet "confirmed by transport"
|
next *KeyPair // not yet "confirmed by transport"
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called during recieving to confirm the handshake
|
|
||||||
* was completed correctly
|
|
||||||
*/
|
|
||||||
func (kp *KeyPairs) Used(key *KeyPair) {
|
|
||||||
if key == kp.next {
|
|
||||||
kp.mutex.Lock()
|
|
||||||
kp.previous = kp.current
|
|
||||||
kp.current = key
|
|
||||||
kp.next = nil
|
|
||||||
kp.mutex.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kp *KeyPairs) Current() *KeyPair {
|
func (kp *KeyPairs) Current() *KeyPair {
|
||||||
kp.mutex.RLock()
|
kp.mutex.RLock()
|
||||||
defer kp.mutex.RUnlock()
|
defer kp.mutex.RUnlock()
|
||||||
|
|
23
src/misc.go
23
src/misc.go
|
@ -4,6 +4,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/* We use int32 as atomic bools
|
||||||
|
* (since booleans are not natively supported by sync/atomic)
|
||||||
|
*/
|
||||||
|
const (
|
||||||
|
AtomicFalse = iota
|
||||||
|
AtomicTrue
|
||||||
|
)
|
||||||
|
|
||||||
func min(a uint, b uint) uint {
|
func min(a uint, b uint) uint {
|
||||||
if a > b {
|
if a > b {
|
||||||
return b
|
return b
|
||||||
|
@ -11,14 +19,21 @@ func min(a uint, b uint) uint {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendSignal(c chan struct{}) {
|
func signalSend(c chan struct{}) {
|
||||||
select {
|
select {
|
||||||
case c <- struct{}{}:
|
case c <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopTimer(timer *time.Timer) {
|
func signalClear(c chan struct{}) {
|
||||||
|
select {
|
||||||
|
case <-c:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func timerStop(timer *time.Timer) {
|
||||||
if !timer.Stop() {
|
if !timer.Stop() {
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
|
@ -27,8 +42,8 @@ func stopTimer(timer *time.Timer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stoppedTimer() *time.Timer {
|
func NewStoppedTimer() *time.Timer {
|
||||||
timer := time.NewTimer(time.Hour)
|
timer := time.NewTimer(time.Hour)
|
||||||
stopTimer(timer)
|
timerStop(timer)
|
||||||
return timer
|
return timer
|
||||||
}
|
}
|
||||||
|
|
|
@ -478,7 +478,7 @@ func (peer *Peer) NewKeyPair() *KeyPair {
|
||||||
}
|
}
|
||||||
kp.previous = kp.current
|
kp.previous = kp.current
|
||||||
kp.current = keyPair
|
kp.current = keyPair
|
||||||
sendSignal(peer.signal.newKeyPair)
|
signalSend(peer.signal.newKeyPair)
|
||||||
} else {
|
} else {
|
||||||
kp.next = keyPair
|
kp.next = keyPair
|
||||||
}
|
}
|
||||||
|
|
27
src/peer.go
27
src/peer.go
|
@ -20,25 +20,36 @@ type Peer struct {
|
||||||
txBytes uint64
|
txBytes uint64
|
||||||
rxBytes uint64
|
rxBytes uint64
|
||||||
time struct {
|
time struct {
|
||||||
|
mutex sync.RWMutex
|
||||||
lastSend time.Time // last send message
|
lastSend time.Time // last send message
|
||||||
lastHandshake time.Time // last completed handshake
|
lastHandshake time.Time // last completed handshake
|
||||||
|
nextKeepalive time.Time
|
||||||
}
|
}
|
||||||
signal struct {
|
signal struct {
|
||||||
newKeyPair chan struct{} // (size 1) : a new key pair was generated
|
newKeyPair chan struct{} // (size 1) : a new key pair was generated
|
||||||
handshakeBegin chan struct{} // (size 1) : request that a new handshake be started ("queue handshake")
|
handshakeBegin chan struct{} // (size 1) : request that a new handshake be started ("queue handshake")
|
||||||
handshakeCompleted chan struct{} // (size 1) : handshake completed
|
handshakeCompleted chan struct{} // (size 1) : handshake completed
|
||||||
flushNonceQueue chan struct{} // (size 1) : empty queued packets
|
flushNonceQueue chan struct{} // (size 1) : empty queued packets
|
||||||
|
messageSend chan struct{} // (size 1) : a message was send to the peer
|
||||||
|
messageReceived chan struct{} // (size 1) : an authenticated message was received
|
||||||
stop chan struct{} // (size 0) : close to stop all goroutines for peer
|
stop chan struct{} // (size 0) : close to stop all goroutines for peer
|
||||||
}
|
}
|
||||||
timer struct {
|
timer struct {
|
||||||
sendKeepalive *time.Timer
|
/* Both keep-alive timers acts as one (see timers.go)
|
||||||
handshakeTimeout *time.Timer
|
* They are kept seperate to simplify the implementation.
|
||||||
|
*/
|
||||||
|
keepalivePersistent *time.Timer // set for persistent keepalives
|
||||||
|
keepaliveAcknowledgement *time.Timer // set upon recieving messages
|
||||||
|
zeroAllKeys *time.Timer // zero all key material after RejectAfterTime*3
|
||||||
}
|
}
|
||||||
queue struct {
|
queue struct {
|
||||||
nonce chan *QueueOutboundElement // nonce / pre-handshake queue
|
nonce chan *QueueOutboundElement // nonce / pre-handshake queue
|
||||||
outbound chan *QueueOutboundElement // sequential ordering of work
|
outbound chan *QueueOutboundElement // sequential ordering of work
|
||||||
inbound chan *QueueInboundElement // sequential ordering of work
|
inbound chan *QueueInboundElement // sequential ordering of work
|
||||||
}
|
}
|
||||||
|
flags struct {
|
||||||
|
keepaliveWaiting int32
|
||||||
|
}
|
||||||
mac MACStatePeer
|
mac MACStatePeer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +62,12 @@ func (device *Device) NewPeer(pk NoisePublicKey) *Peer {
|
||||||
|
|
||||||
peer.mac.Init(pk)
|
peer.mac.Init(pk)
|
||||||
peer.device = device
|
peer.device = device
|
||||||
peer.timer.sendKeepalive = stoppedTimer()
|
|
||||||
|
peer.timer.keepalivePersistent = NewStoppedTimer()
|
||||||
|
peer.timer.keepaliveAcknowledgement = NewStoppedTimer()
|
||||||
|
peer.timer.zeroAllKeys = NewStoppedTimer()
|
||||||
|
|
||||||
|
peer.flags.keepaliveWaiting = AtomicFalse
|
||||||
|
|
||||||
// assign id for debugging
|
// assign id for debugging
|
||||||
|
|
||||||
|
@ -82,7 +98,7 @@ func (device *Device) NewPeer(pk NoisePublicKey) *Peer {
|
||||||
peer.queue.outbound = make(chan *QueueOutboundElement, QueueOutboundSize)
|
peer.queue.outbound = make(chan *QueueOutboundElement, QueueOutboundSize)
|
||||||
peer.queue.inbound = make(chan *QueueInboundElement, QueueInboundSize)
|
peer.queue.inbound = make(chan *QueueInboundElement, QueueInboundSize)
|
||||||
|
|
||||||
// prepare signaling
|
// prepare signaling & routines
|
||||||
|
|
||||||
peer.signal.stop = make(chan struct{})
|
peer.signal.stop = make(chan struct{})
|
||||||
peer.signal.newKeyPair = make(chan struct{}, 1)
|
peer.signal.newKeyPair = make(chan struct{}, 1)
|
||||||
|
@ -90,9 +106,8 @@ func (device *Device) NewPeer(pk NoisePublicKey) *Peer {
|
||||||
peer.signal.handshakeCompleted = make(chan struct{}, 1)
|
peer.signal.handshakeCompleted = make(chan struct{}, 1)
|
||||||
peer.signal.flushNonceQueue = make(chan struct{}, 1)
|
peer.signal.flushNonceQueue = make(chan struct{}, 1)
|
||||||
|
|
||||||
// outbound pipeline
|
|
||||||
|
|
||||||
go peer.RoutineNonce()
|
go peer.RoutineNonce()
|
||||||
|
go peer.RoutineTimerHandler()
|
||||||
go peer.RoutineHandshakeInitiator()
|
go peer.RoutineHandshakeInitiator()
|
||||||
go peer.RoutineSequentialSender()
|
go peer.RoutineSequentialSender()
|
||||||
go peer.RoutineSequentialReceiver()
|
go peer.RoutineSequentialReceiver()
|
||||||
|
|
|
@ -10,11 +10,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
ElementStateOkay = iota
|
|
||||||
ElementStateDropped
|
|
||||||
)
|
|
||||||
|
|
||||||
type QueueHandshakeElement struct {
|
type QueueHandshakeElement struct {
|
||||||
msgType uint32
|
msgType uint32
|
||||||
packet []byte
|
packet []byte
|
||||||
|
@ -22,7 +17,7 @@ type QueueHandshakeElement struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueueInboundElement struct {
|
type QueueInboundElement struct {
|
||||||
state uint32
|
dropped int32
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
packet []byte
|
packet []byte
|
||||||
counter uint64
|
counter uint64
|
||||||
|
@ -30,11 +25,11 @@ type QueueInboundElement struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (elem *QueueInboundElement) Drop() {
|
func (elem *QueueInboundElement) Drop() {
|
||||||
atomic.StoreUint32(&elem.state, ElementStateDropped)
|
atomic.StoreInt32(&elem.dropped, AtomicTrue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (elem *QueueInboundElement) IsDropped() bool {
|
func (elem *QueueInboundElement) IsDropped() bool {
|
||||||
return atomic.LoadUint32(&elem.state) == ElementStateDropped
|
return atomic.LoadInt32(&elem.dropped) == AtomicTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToInboundQueue(
|
func addToInboundQueue(
|
||||||
|
@ -101,9 +96,9 @@ func (device *Device) RoutineBusyMonitor() {
|
||||||
// update busy state
|
// update busy state
|
||||||
|
|
||||||
if busy {
|
if busy {
|
||||||
atomic.StoreInt32(&device.congestionState, CongestionStateUnderLoad)
|
atomic.StoreInt32(&device.underLoad, AtomicTrue)
|
||||||
} else {
|
} else {
|
||||||
atomic.StoreInt32(&device.congestionState, CongestionStateOkay)
|
atomic.StoreInt32(&device.underLoad, AtomicFalse)
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.Reset(interval)
|
timer.Reset(interval)
|
||||||
|
@ -216,7 +211,7 @@ func (device *Device) RoutineReceiveIncomming() {
|
||||||
work := new(QueueInboundElement)
|
work := new(QueueInboundElement)
|
||||||
work.packet = packet
|
work.packet = packet
|
||||||
work.keyPair = keyPair
|
work.keyPair = keyPair
|
||||||
work.state = ElementStateOkay
|
work.dropped = AtomicFalse
|
||||||
work.mutex.Lock()
|
work.mutex.Lock()
|
||||||
|
|
||||||
// add to decryption queues
|
// add to decryption queues
|
||||||
|
@ -303,7 +298,7 @@ func (device *Device) RoutineHandshake() {
|
||||||
|
|
||||||
// verify mac2
|
// verify mac2
|
||||||
|
|
||||||
busy := atomic.LoadInt32(&device.congestionState) == CongestionStateUnderLoad
|
busy := atomic.LoadInt32(&device.underLoad) == AtomicTrue
|
||||||
|
|
||||||
if busy && !device.mac.CheckMAC2(elem.packet, elem.source) {
|
if busy && !device.mac.CheckMAC2(elem.packet, elem.source) {
|
||||||
sender := binary.LittleEndian.Uint32(elem.packet[4:8]) // "sender" always follows "type"
|
sender := binary.LittleEndian.Uint32(elem.packet[4:8]) // "sender" always follows "type"
|
||||||
|
@ -397,13 +392,12 @@ func (device *Device) RoutineHandshake() {
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sendSignal(peer.signal.handshakeCompleted)
|
|
||||||
logDebug.Println("Recieved valid response message for peer", peer.id)
|
|
||||||
kp := peer.NewKeyPair()
|
kp := peer.NewKeyPair()
|
||||||
if kp == nil {
|
if kp == nil {
|
||||||
logDebug.Println("Failed to derieve key-pair")
|
logDebug.Println("Failed to derieve key-pair")
|
||||||
}
|
}
|
||||||
peer.SendKeepAlive()
|
peer.SendKeepAlive()
|
||||||
|
peer.EventHandshakeComplete()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
device.log.Error.Println("Invalid message type in handshake queue")
|
device.log.Error.Println("Invalid message type in handshake queue")
|
||||||
|
@ -438,9 +432,25 @@ func (peer *Peer) RoutineSequentialReceiver() {
|
||||||
|
|
||||||
// check for replay
|
// check for replay
|
||||||
|
|
||||||
// update timers
|
// time (passive) keep-alive
|
||||||
|
|
||||||
// refresh key material
|
peer.TimerStartKeepalive()
|
||||||
|
|
||||||
|
// refresh key material (rekey)
|
||||||
|
|
||||||
|
peer.KeepKeyFreshReceiving()
|
||||||
|
|
||||||
|
// check if confirming handshake
|
||||||
|
|
||||||
|
kp := &peer.keyPairs
|
||||||
|
kp.mutex.Lock()
|
||||||
|
if kp.next == elem.keyPair {
|
||||||
|
peer.EventHandshakeComplete()
|
||||||
|
kp.previous = kp.current
|
||||||
|
kp.current = kp.next
|
||||||
|
kp.next = nil
|
||||||
|
}
|
||||||
|
kp.mutex.Unlock()
|
||||||
|
|
||||||
// check for keep-alive
|
// check for keep-alive
|
||||||
|
|
||||||
|
@ -491,7 +501,7 @@ func (peer *Peer) RoutineSequentialReceiver() {
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
device.log.Debug.Println("Receieved packet with unknown IP version")
|
logDebug.Println("Receieved packet with unknown IP version")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
58
src/send.go
58
src/send.go
|
@ -31,7 +31,7 @@ import (
|
||||||
* (to allow the construction of transport messages in-place)
|
* (to allow the construction of transport messages in-place)
|
||||||
*/
|
*/
|
||||||
type QueueOutboundElement struct {
|
type QueueOutboundElement struct {
|
||||||
state uint32
|
dropped int32
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
data [MaxMessageSize]byte
|
data [MaxMessageSize]byte
|
||||||
packet []byte // slice of "data" (always!)
|
packet []byte // slice of "data" (always!)
|
||||||
|
@ -61,11 +61,11 @@ func (device *Device) NewOutboundElement() *QueueOutboundElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (elem *QueueOutboundElement) Drop() {
|
func (elem *QueueOutboundElement) Drop() {
|
||||||
atomic.StoreUint32(&elem.state, ElementStateDropped)
|
atomic.StoreInt32(&elem.dropped, AtomicTrue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (elem *QueueOutboundElement) IsDropped() bool {
|
func (elem *QueueOutboundElement) IsDropped() bool {
|
||||||
return atomic.LoadUint32(&elem.state) == ElementStateDropped
|
return atomic.LoadInt32(&elem.dropped) == AtomicTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToOutboundQueue(
|
func addToOutboundQueue(
|
||||||
|
@ -86,6 +86,25 @@ func addToOutboundQueue(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addToEncryptionQueue(
|
||||||
|
queue chan *QueueOutboundElement,
|
||||||
|
element *QueueOutboundElement,
|
||||||
|
) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case queue <- element:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
select {
|
||||||
|
case old := <-queue:
|
||||||
|
old.Drop()
|
||||||
|
old.mutex.Unlock()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Reads packets from the TUN and inserts
|
/* Reads packets from the TUN and inserts
|
||||||
* into nonce queue for peer
|
* into nonce queue for peer
|
||||||
*
|
*
|
||||||
|
@ -196,9 +215,7 @@ func (peer *Peer) RoutineNonce() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logDebug.Println("Key pair:", keyPair)
|
signalSend(peer.signal.handshakeBegin)
|
||||||
|
|
||||||
sendSignal(peer.signal.handshakeBegin)
|
|
||||||
logDebug.Println("Waiting for key-pair, peer", peer.id)
|
logDebug.Println("Waiting for key-pair, peer", peer.id)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
@ -225,12 +242,13 @@ func (peer *Peer) RoutineNonce() {
|
||||||
|
|
||||||
elem.keyPair = keyPair
|
elem.keyPair = keyPair
|
||||||
elem.nonce = atomic.AddUint64(&keyPair.sendNonce, 1) - 1
|
elem.nonce = atomic.AddUint64(&keyPair.sendNonce, 1) - 1
|
||||||
|
elem.dropped = AtomicFalse
|
||||||
elem.peer = peer
|
elem.peer = peer
|
||||||
elem.mutex.Lock()
|
elem.mutex.Lock()
|
||||||
|
|
||||||
// add to parallel processing and sequential consuming queue
|
// add to parallel and sequential queue
|
||||||
|
|
||||||
addToOutboundQueue(device.queue.encryption, elem)
|
addToEncryptionQueue(device.queue.encryption, elem)
|
||||||
addToOutboundQueue(peer.queue.outbound, elem)
|
addToOutboundQueue(peer.queue.outbound, elem)
|
||||||
elem = nil
|
elem = nil
|
||||||
}
|
}
|
||||||
|
@ -246,6 +264,9 @@ func (peer *Peer) RoutineNonce() {
|
||||||
func (device *Device) RoutineEncryption() {
|
func (device *Device) RoutineEncryption() {
|
||||||
var nonce [chacha20poly1305.NonceSize]byte
|
var nonce [chacha20poly1305.NonceSize]byte
|
||||||
for work := range device.queue.encryption {
|
for work := range device.queue.encryption {
|
||||||
|
|
||||||
|
// check if dropped
|
||||||
|
|
||||||
if work.IsDropped() {
|
if work.IsDropped() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -289,25 +310,25 @@ func (device *Device) RoutineEncryption() {
|
||||||
* The routine terminates then the outbound queue is closed.
|
* The routine terminates then the outbound queue is closed.
|
||||||
*/
|
*/
|
||||||
func (peer *Peer) RoutineSequentialSender() {
|
func (peer *Peer) RoutineSequentialSender() {
|
||||||
logDebug := peer.device.log.Debug
|
|
||||||
logDebug.Println("Routine, sequential sender, started for peer", peer.id)
|
|
||||||
|
|
||||||
device := peer.device
|
device := peer.device
|
||||||
|
|
||||||
|
logDebug := device.log.Debug
|
||||||
|
logDebug.Println("Routine, sequential sender, started for peer", peer.id)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-peer.signal.stop:
|
case <-peer.signal.stop:
|
||||||
logDebug.Println("Routine, sequential sender, stopped for peer", peer.id)
|
logDebug.Println("Routine, sequential sender, stopped for peer", peer.id)
|
||||||
return
|
return
|
||||||
case work := <-peer.queue.outbound:
|
case work := <-peer.queue.outbound:
|
||||||
|
work.mutex.Lock()
|
||||||
if work.IsDropped() {
|
if work.IsDropped() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
work.mutex.Lock()
|
|
||||||
func() {
|
func() {
|
||||||
if work.packet == nil {
|
|
||||||
return
|
// send to endpoint
|
||||||
}
|
|
||||||
|
|
||||||
peer.mutex.RLock()
|
peer.mutex.RLock()
|
||||||
defer peer.mutex.RUnlock()
|
defer peer.mutex.RUnlock()
|
||||||
|
@ -331,12 +352,9 @@ func (peer *Peer) RoutineSequentialSender() {
|
||||||
}
|
}
|
||||||
atomic.AddUint64(&peer.txBytes, uint64(len(work.packet)))
|
atomic.AddUint64(&peer.txBytes, uint64(len(work.packet)))
|
||||||
|
|
||||||
// shift keep-alive timer
|
// reset keep-alive (passive keep-alives / acknowledgements)
|
||||||
|
|
||||||
if peer.persistentKeepaliveInterval != 0 {
|
peer.TimerResetKeepalive()
|
||||||
interval := time.Duration(peer.persistentKeepaliveInterval) * time.Second
|
|
||||||
peer.timer.sendKeepalive.Reset(interval)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
303
src/timers.go
Normal file
303
src/timers.go
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"golang.org/x/crypto/blake2s"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Called when a new authenticated message has been send
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
func (peer *Peer) KeepKeyFreshSending() {
|
||||||
|
send := func() bool {
|
||||||
|
peer.keyPairs.mutex.RLock()
|
||||||
|
defer peer.keyPairs.mutex.RUnlock()
|
||||||
|
|
||||||
|
kp := peer.keyPairs.current
|
||||||
|
if kp == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !kp.isInitiator {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := atomic.LoadUint64(&kp.sendNonce)
|
||||||
|
return nonce > RekeyAfterMessages || time.Now().Sub(kp.created) > RekeyAfterTime
|
||||||
|
}()
|
||||||
|
if send {
|
||||||
|
signalSend(peer.signal.handshakeBegin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called when a new authenticated message has been recevied
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
func (peer *Peer) KeepKeyFreshReceiving() {
|
||||||
|
send := func() bool {
|
||||||
|
peer.keyPairs.mutex.RLock()
|
||||||
|
defer peer.keyPairs.mutex.RUnlock()
|
||||||
|
|
||||||
|
kp := peer.keyPairs.current
|
||||||
|
if kp == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !kp.isInitiator {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := atomic.LoadUint64(&kp.sendNonce)
|
||||||
|
return nonce > RekeyAfterMessages || time.Now().Sub(kp.created) > RekeyAfterTimeReceiving
|
||||||
|
}()
|
||||||
|
if send {
|
||||||
|
signalSend(peer.signal.handshakeBegin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called after succesfully completing a handshake.
|
||||||
|
* i.e. after:
|
||||||
|
* - Valid handshake response
|
||||||
|
* - First transport message under the "next" key
|
||||||
|
*/
|
||||||
|
func (peer *Peer) EventHandshakeComplete() {
|
||||||
|
peer.device.log.Debug.Println("Handshake completed")
|
||||||
|
peer.timer.zeroAllKeys.Reset(RejectAfterTime * 3)
|
||||||
|
signalSend(peer.signal.handshakeCompleted)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Queues a keep-alive if no packets are queued for peer
|
||||||
|
*/
|
||||||
|
func (peer *Peer) SendKeepAlive() bool {
|
||||||
|
elem := peer.device.NewOutboundElement()
|
||||||
|
elem.packet = nil
|
||||||
|
if len(peer.queue.nonce) == 0 {
|
||||||
|
select {
|
||||||
|
case peer.queue.nonce <- elem:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Starts the "keep-alive" timer
|
||||||
|
* (if not already running),
|
||||||
|
* in response to incomming messages
|
||||||
|
*/
|
||||||
|
func (peer *Peer) TimerStartKeepalive() {
|
||||||
|
|
||||||
|
// check if acknowledgement timer set yet
|
||||||
|
|
||||||
|
var waiting int32 = AtomicTrue
|
||||||
|
waiting = atomic.SwapInt32(&peer.flags.keepaliveWaiting, waiting)
|
||||||
|
if waiting == AtomicTrue {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// timer not yet set, start it
|
||||||
|
|
||||||
|
wait := KeepaliveTimeout
|
||||||
|
interval := atomic.LoadUint64(&peer.persistentKeepaliveInterval)
|
||||||
|
if interval > 0 {
|
||||||
|
duration := time.Duration(interval) * time.Second
|
||||||
|
if duration < wait {
|
||||||
|
wait = duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resets both keep-alive timers
|
||||||
|
*/
|
||||||
|
func (peer *Peer) TimerResetKeepalive() {
|
||||||
|
|
||||||
|
// reset persistent timer
|
||||||
|
|
||||||
|
interval := atomic.LoadUint64(&peer.persistentKeepaliveInterval)
|
||||||
|
if interval > 0 {
|
||||||
|
peer.timer.keepalivePersistent.Reset(
|
||||||
|
time.Duration(interval) * time.Second,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop acknowledgement timer
|
||||||
|
|
||||||
|
timerStop(peer.timer.keepaliveAcknowledgement)
|
||||||
|
atomic.StoreInt32(&peer.flags.keepaliveWaiting, AtomicFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (peer *Peer) BeginHandshakeInitiation() (*QueueOutboundElement, error) {
|
||||||
|
|
||||||
|
// create initiation
|
||||||
|
|
||||||
|
elem := peer.device.NewOutboundElement()
|
||||||
|
msg, err := peer.device.CreateMessageInitiation(peer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal & schedule for sending
|
||||||
|
|
||||||
|
writer := bytes.NewBuffer(elem.data[:0])
|
||||||
|
binary.Write(writer, binary.LittleEndian, msg)
|
||||||
|
elem.packet = writer.Bytes()
|
||||||
|
peer.mac.AddMacs(elem.packet)
|
||||||
|
addToOutboundQueue(peer.queue.outbound, elem)
|
||||||
|
return elem, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (peer *Peer) RoutineTimerHandler() {
|
||||||
|
device := peer.device
|
||||||
|
|
||||||
|
logDebug := device.log.Debug
|
||||||
|
logDebug.Println("Routine, timer handler, started for peer", peer.id)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
|
||||||
|
case <-peer.signal.stop:
|
||||||
|
return
|
||||||
|
|
||||||
|
// keep-alives
|
||||||
|
|
||||||
|
case <-peer.timer.keepalivePersistent.C:
|
||||||
|
|
||||||
|
logDebug.Println("Sending persistent keep-alive to peer", peer.id)
|
||||||
|
|
||||||
|
peer.SendKeepAlive()
|
||||||
|
peer.TimerResetKeepalive()
|
||||||
|
|
||||||
|
case <-peer.timer.keepaliveAcknowledgement.C:
|
||||||
|
|
||||||
|
logDebug.Println("Sending passive persistent keep-alive to peer", peer.id)
|
||||||
|
|
||||||
|
peer.SendKeepAlive()
|
||||||
|
peer.TimerResetKeepalive()
|
||||||
|
|
||||||
|
// clear key material
|
||||||
|
|
||||||
|
case <-peer.timer.zeroAllKeys.C:
|
||||||
|
|
||||||
|
logDebug.Println("Clearing all key material for peer", peer.id)
|
||||||
|
|
||||||
|
// zero out key pairs
|
||||||
|
|
||||||
|
func() {
|
||||||
|
kp := &peer.keyPairs
|
||||||
|
kp.mutex.Lock()
|
||||||
|
// best we can do is wait for GC :( ?
|
||||||
|
kp.current = nil
|
||||||
|
kp.previous = nil
|
||||||
|
kp.next = nil
|
||||||
|
kp.mutex.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// zero out handshake
|
||||||
|
|
||||||
|
func() {
|
||||||
|
hs := &peer.handshake
|
||||||
|
hs.mutex.Lock()
|
||||||
|
hs.localEphemeral = NoisePrivateKey{}
|
||||||
|
hs.remoteEphemeral = NoisePublicKey{}
|
||||||
|
hs.chainKey = [blake2s.Size]byte{}
|
||||||
|
hs.hash = [blake2s.Size]byte{}
|
||||||
|
hs.mutex.Unlock()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is the state machine for handshake initiation
|
||||||
|
*
|
||||||
|
* Associated with this routine is the signal "handshakeBegin"
|
||||||
|
* The routine will read from the "handshakeBegin" channel
|
||||||
|
* at most every RekeyTimeout seconds
|
||||||
|
*/
|
||||||
|
func (peer *Peer) RoutineHandshakeInitiator() {
|
||||||
|
device := peer.device
|
||||||
|
|
||||||
|
var elem *QueueOutboundElement
|
||||||
|
|
||||||
|
logError := device.log.Error
|
||||||
|
logDebug := device.log.Debug
|
||||||
|
logDebug.Println("Routine, handshake initator, started for peer", peer.id)
|
||||||
|
|
||||||
|
for run := true; run; {
|
||||||
|
var err error
|
||||||
|
var attempts uint
|
||||||
|
var deadline time.Time
|
||||||
|
|
||||||
|
// wait for signal
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-peer.signal.handshakeBegin:
|
||||||
|
case <-peer.signal.stop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for handshake
|
||||||
|
|
||||||
|
run = func() bool {
|
||||||
|
for {
|
||||||
|
// clear completed signal
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-peer.signal.handshakeCompleted:
|
||||||
|
case <-peer.signal.stop:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// create initiation
|
||||||
|
|
||||||
|
if elem != nil {
|
||||||
|
elem.Drop()
|
||||||
|
}
|
||||||
|
elem, err = peer.BeginHandshakeInitiation()
|
||||||
|
if err != nil {
|
||||||
|
logError.Println("Failed to create initiation message:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// set timeout
|
||||||
|
|
||||||
|
attempts += 1
|
||||||
|
if attempts == 1 {
|
||||||
|
deadline = time.Now().Add(MaxHandshakeAttemptTime)
|
||||||
|
}
|
||||||
|
timeout := time.NewTimer(RekeyTimeout)
|
||||||
|
logDebug.Println("Handshake initiation attempt", attempts, "queued for peer", peer.id)
|
||||||
|
|
||||||
|
// wait for handshake or timeout
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-peer.signal.stop:
|
||||||
|
return true
|
||||||
|
|
||||||
|
case <-peer.signal.handshakeCompleted:
|
||||||
|
<-timeout.C
|
||||||
|
return true
|
||||||
|
|
||||||
|
case <-timeout.C:
|
||||||
|
logDebug.Println("Timeout")
|
||||||
|
|
||||||
|
// check if sufficient time for retry
|
||||||
|
|
||||||
|
if deadline.Before(time.Now().Add(RekeyTimeout)) {
|
||||||
|
signalSend(peer.signal.flushNonceQueue)
|
||||||
|
timerStop(peer.timer.keepalivePersistent)
|
||||||
|
timerStop(peer.timer.keepaliveAcknowledgement)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}()
|
||||||
|
|
||||||
|
signalClear(peer.signal.handshakeBegin)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue