summaryrefslogtreecommitdiffhomepage
path: root/timers.go
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2018-05-07 22:27:03 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2018-05-10 16:08:03 +0200
commit233f079a9479279d2aab68f4accb139ee87ad664 (patch)
tree338dfb681ffafbb53b81d353aa5612866ff935f5 /timers.go
parent375dcbd4aefc8054700dcb072a5e74a9ed7e9d39 (diff)
Rewrite timers and related state machines
Diffstat (limited to 'timers.go')
-rw-r--r--timers.go476
1 files changed, 171 insertions, 305 deletions
diff --git a/timers.go b/timers.go
index 38c9b46..5c72efd 100644
--- a/timers.go
+++ b/timers.go
@@ -1,355 +1,221 @@
/* SPDX-License-Identifier: GPL-2.0
*
- * Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ * Copyright (C) 2015-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ *
+ * This is based heavily on timers.c from the kernel implementation.
*/
package main
import (
- "bytes"
- "encoding/binary"
"math/rand"
"sync/atomic"
"time"
)
-/* NOTE:
- * Notion of validity
+/* This Timer structure and related functions should roughly copy the interface of
+ * the Linux kernel's struct timer_list.
*/
-/* Called when a new authenticated message has been send
- *
- */
-func (peer *Peer) KeepKeyFreshSending() {
- kp := peer.keyPairs.Current()
- if kp == nil {
- return
- }
- nonce := atomic.LoadUint64(&kp.sendNonce)
- if nonce > RekeyAfterMessages {
- peer.event.handshakeBegin.Fire()
- }
- if kp.isInitiator && time.Now().Sub(kp.created) > RekeyAfterTime {
- peer.event.handshakeBegin.Fire()
- }
+type Timer struct {
+ timer *time.Timer
+ isPending bool
}
-/* Called when a new authenticated message has been received
- *
- * NOTE: Not thread safe, but called by sequential receiver!
- */
-func (peer *Peer) KeepKeyFreshReceiving() {
- if peer.timer.sendLastMinuteHandshake.Get() {
- return
- }
- kp := peer.keyPairs.Current()
- if kp == nil {
- return
- }
- if !kp.isInitiator {
- return
- }
- nonce := atomic.LoadUint64(&kp.sendNonce)
- send := nonce > RekeyAfterMessages || time.Now().Sub(kp.created) > RekeyAfterTimeReceiving
- if send {
- // do a last minute attempt at initiating a new handshake
- peer.timer.sendLastMinuteHandshake.Set(true)
- peer.event.handshakeBegin.Fire()
- }
+func (peer *Peer) NewTimer(expirationFunction func(*Peer)) *Timer {
+ timer := &Timer{}
+ timer.timer = time.AfterFunc(time.Hour, func() {
+ timer.isPending = false
+ expirationFunction(peer)
+ })
+ timer.timer.Stop()
+ return timer
}
-/* Queues a keep-alive if no packets are queued for peer
- */
-func (peer *Peer) SendKeepAlive() bool {
- if len(peer.queue.nonce) != 0 {
- return false
- }
- elem := peer.device.NewOutboundElement()
- elem.packet = nil
- select {
- case peer.queue.nonce <- elem:
- return true
- default:
- return false
- }
+func (timer *Timer) Mod(d time.Duration) {
+ timer.isPending = true
+ timer.timer.Reset(d)
}
-/* Called after successfully completing a handshake.
- * i.e. after:
- *
- * - Valid handshake response
- * - First transport message under the "next" key
- */
-// peer.device.log.Info.Println(peer, ": New handshake completed")
-
-/* Event:
- * An ephemeral key is generated
- *
- * i.e. after:
- *
- * CreateMessageInitiation
- * CreateMessageResponse
- *
- * Action:
- * Schedule the deletion of all key material
- * upon failure to complete a handshake
- */
-func (peer *Peer) TimerEphemeralKeyCreated() {
- peer.event.ephemeralKeyCreated.Fire()
- // peer.timer.zeroAllKeys.Reset(RejectAfterTime * 3)
+func (timer *Timer) Del() {
+ timer.isPending = false
+ timer.timer.Stop()
}
-/* Sends a new handshake initiation message to the peer (endpoint)
- */
-func (peer *Peer) sendNewHandshake() error {
-
- // create initiation message
-
- msg, err := peer.device.CreateMessageInitiation(peer)
- if err != nil {
- return err
- }
+func (peer *Peer) timersActive() bool {
+ return peer.isRunning.Get() && peer.device != nil && peer.device.isUp.Get() && len(peer.device.peers.keyMap) > 0
+}
- // marshal handshake message
+func expiredRetransmitHandshake(peer *Peer) {
+ if peer.timers.handshakeAttempts > MaxTimerHandshakes {
+ peer.device.log.Debug.Printf("%s: Handshake did not complete after %d attempts, giving up\n", peer, MaxTimerHandshakes+2)
- var buff [MessageInitiationSize]byte
- writer := bytes.NewBuffer(buff[:0])
- binary.Write(writer, binary.LittleEndian, msg)
- packet := writer.Bytes()
- peer.mac.AddMacs(packet)
+ if peer.timersActive() {
+ peer.timers.sendKeepalive.Del()
+ }
- // send to endpoint
+ /* We drop all packets without a keypair and don't try again,
+ * if we try unsuccessfully for too long to make a handshake.
+ */
+ peer.FlushNonceQueue()
- peer.event.anyAuthenticatedPacketTraversal.Fire()
+ /* We set a timer for destroying any residue that might be left
+ * of a partial exchange.
+ */
+ if peer.timersActive() && !peer.timers.zeroKeyMaterial.isPending {
+ peer.timers.zeroKeyMaterial.Mod(RejectAfterTime * 3)
+ }
+ } else {
+ peer.timers.handshakeAttempts++
+ peer.device.log.Debug.Printf("%s: Handshake did not complete after %d seconds, retrying (try %d)\n", peer, int(RekeyTimeout.Seconds()), peer.timers.handshakeAttempts+1)
+
+ /* We clear the endpoint address src address, in case this is the cause of trouble. */
+ peer.mutex.Lock()
+ if peer.endpoint != nil {
+ peer.endpoint.ClearSrc()
+ }
+ peer.mutex.Unlock()
- return peer.SendBuffer(packet)
+ peer.SendHandshakeInitiation(true)
+ }
}
-func newTimer() *time.Timer {
- timer := time.NewTimer(time.Hour)
- timer.Stop()
- return timer
+func expiredSendKeepalive(peer *Peer) {
+ peer.SendKeepalive()
+ if peer.timers.needAnotherKeepalive {
+ peer.timers.needAnotherKeepalive = false
+ if peer.timersActive() {
+ peer.timers.sendKeepalive.Mod(KeepaliveTimeout)
+ }
+ }
}
-func (peer *Peer) RoutineTimerHandler() {
-
- device := peer.device
-
- logInfo := device.log.Info
- logDebug := device.log.Debug
-
- defer func() {
- logDebug.Println(peer, ": Routine: timer handler - stopped")
- peer.routines.stopping.Done()
- }()
-
- logDebug.Println(peer, ": Routine: timer handler - started")
-
- // reset all timers
-
- enableHandshake := true
- pendingHandshakeNew := false
- pendingKeepalivePassive := false
- needAnotherKeepalive := false
-
- timerKeepalivePassive := newTimer()
- timerHandshakeDeadline := newTimer()
- timerHandshakeTimeout := newTimer()
- timerHandshakeNew := newTimer()
- timerZeroAllKeys := newTimer()
- timerKeepalivePersistent := newTimer()
-
- interval := peer.persistentKeepaliveInterval
- if interval > 0 {
- duration := time.Duration(interval) * time.Second
- timerKeepalivePersistent.Reset(duration)
+func expiredNewHandshake(peer *Peer) {
+ peer.device.log.Debug.Printf("%s: Retrying handshake because we stopped hearing back after %d seconds\n", peer, int((KeepaliveTimeout + RekeyTimeout).Seconds()))
+ /* We clear the endpoint address src address, in case this is the cause of trouble. */
+ peer.mutex.Lock()
+ if peer.endpoint != nil {
+ peer.endpoint.ClearSrc()
}
+ peer.mutex.Unlock()
+ peer.SendHandshakeInitiation(false)
- // signal synchronised setup complete
-
- peer.routines.starting.Done()
-
- // handle timer events
-
- for {
- select {
-
- /* stopping */
-
- case <-peer.routines.stop:
- return
-
- /* events */
-
- case <-peer.event.dataSent.C:
- timerKeepalivePassive.Stop()
- if !pendingHandshakeNew {
- timerHandshakeNew.Reset(NewHandshakeTime)
- }
-
- case <-peer.event.dataReceived.C:
- if pendingKeepalivePassive {
- needAnotherKeepalive = true
- } else {
- timerKeepalivePassive.Reset(KeepaliveTimeout)
- }
-
- case <-peer.event.anyAuthenticatedPacketTraversal.C:
- interval := peer.persistentKeepaliveInterval
- if interval > 0 {
- duration := time.Duration(interval) * time.Second
- timerKeepalivePersistent.Reset(duration)
- }
-
- case <-peer.event.handshakeBegin.C:
-
- if !enableHandshake {
- continue
- }
-
- logDebug.Println(peer, ": Event, Handshake Begin")
-
- err := peer.sendNewHandshake()
-
- // set timeout
-
- jitter := time.Millisecond * time.Duration(rand.Int31n(334))
- timerKeepalivePassive.Stop()
- timerHandshakeTimeout.Reset(RekeyTimeout + jitter)
-
- if err != nil {
- logInfo.Println(peer, ": Failed to send handshake initiation", err)
- } else {
- logDebug.Println(peer, ": Send handshake initiation (initial)")
- }
-
- timerHandshakeDeadline.Reset(RekeyAttemptTime)
-
- // disable further handshakes
-
- peer.event.handshakeBegin.Clear()
- enableHandshake = false
-
- case <-peer.event.handshakeCompleted.C:
-
- logInfo.Println(peer, ": Handshake completed")
-
- atomic.StoreInt64(
- &peer.stats.lastHandshakeNano,
- time.Now().UnixNano(),
- )
-
- timerHandshakeTimeout.Stop()
- timerHandshakeDeadline.Stop()
- peer.timer.sendLastMinuteHandshake.Set(false)
-
- // allow further handshakes
-
- peer.event.handshakeBegin.Clear()
- enableHandshake = true
-
- /* timers */
-
- case <-timerKeepalivePersistent.C:
-
- interval := peer.persistentKeepaliveInterval
- if interval > 0 {
- logDebug.Println(peer, ": Send keep-alive (persistent)")
- timerKeepalivePassive.Stop()
- peer.SendKeepAlive()
- }
-
- case <-timerKeepalivePassive.C:
-
- logDebug.Println(peer, ": Send keep-alive (passive)")
-
- peer.SendKeepAlive()
-
- if needAnotherKeepalive {
- timerKeepalivePassive.Reset(KeepaliveTimeout)
- needAnotherKeepalive = false
- }
-
- case <-timerZeroAllKeys.C:
-
- logDebug.Println(peer, ": Clear all key-material (timer event)")
-
- hs := &peer.handshake
- hs.mutex.Lock()
-
- kp := &peer.keyPairs
- kp.mutex.Lock()
-
- // remove key-pairs
-
- if kp.previous != nil {
- device.DeleteKeyPair(kp.previous)
- kp.previous = nil
- }
- if kp.current != nil {
- device.DeleteKeyPair(kp.current)
- kp.current = nil
- }
- if kp.next != nil {
- device.DeleteKeyPair(kp.next)
- kp.next = nil
- }
- kp.mutex.Unlock()
-
- // zero out handshake
-
- device.indices.Delete(hs.localIndex)
- hs.Clear()
- hs.mutex.Unlock()
-
- case <-timerHandshakeTimeout.C:
-
- // allow new handshake to be send
+}
- enableHandshake = true
+func expiredZeroKeyMaterial(peer *Peer) {
+ peer.device.log.Debug.Printf(":%s Removing all keys, since we haven't received a new one in %d seconds\n", peer, int((RejectAfterTime * 3).Seconds()))
- // clear source (in case this is causing problems)
+ hs := &peer.handshake
+ hs.mutex.Lock()
- peer.mutex.Lock()
- if peer.endpoint != nil {
- peer.endpoint.ClearSrc()
- }
- peer.mutex.Unlock()
+ kp := &peer.keyPairs
+ kp.mutex.Lock()
- // send new handshake
+ if kp.previous != nil {
+ peer.device.DeleteKeypair(kp.previous)
+ kp.previous = nil
+ }
+ if kp.current != nil {
+ peer.device.DeleteKeypair(kp.current)
+ kp.current = nil
+ }
+ if kp.next != nil {
+ peer.device.DeleteKeypair(kp.next)
+ kp.next = nil
+ }
+ kp.mutex.Unlock()
- err := peer.sendNewHandshake()
+ peer.device.indices.Delete(hs.localIndex)
+ hs.Clear()
+ hs.mutex.Unlock()
+}
- // set timeout
+func expiredPersistentKeepalive(peer *Peer) {
+ if peer.persistentKeepaliveInterval > 0 {
+ if peer.timersActive() {
+ peer.timers.sendKeepalive.Del()
+ }
+ peer.SendKeepalive()
+ }
+}
- jitter := time.Millisecond * time.Duration(rand.Int31n(334))
- timerKeepalivePassive.Stop()
- timerHandshakeTimeout.Reset(RekeyTimeout + jitter)
+/* Should be called after an authenticated data packet is sent. */
+func (peer *Peer) timersDataSent() {
+ if peer.timersActive() {
+ peer.timers.sendKeepalive.Del()
+ }
- if err != nil {
- logInfo.Println(peer, ": Failed to send handshake initiation", err)
- } else {
- logDebug.Println(peer, ": Send handshake initiation (subsequent)")
- }
+ if peer.timersActive() && !peer.timers.newHandshake.isPending {
+ peer.timers.newHandshake.Mod(KeepaliveTimeout + RekeyTimeout)
+ }
+}
- // disable further handshakes
+/* Should be called after an authenticated data packet is received. */
+func (peer *Peer) timersDataReceived() {
+ if peer.timersActive() {
+ if !peer.timers.sendKeepalive.isPending {
+ peer.timers.sendKeepalive.Mod(KeepaliveTimeout)
+ } else {
+ peer.timers.needAnotherKeepalive = true
+ }
+ }
+}
- peer.event.handshakeBegin.Clear()
- enableHandshake = false
+/* Should be called after any type of authenticated packet is received -- keepalive or data. */
+func (peer *Peer) timersAnyAuthenticatedPacketReceived() {
+ if peer.timersActive() {
+ peer.timers.newHandshake.Del()
+ }
+}
- case <-timerHandshakeDeadline.C:
+/* Should be called after a handshake initiation message is sent. */
+func (peer *Peer) timersHandshakeInitiated() {
+ if peer.timersActive() {
+ peer.timers.sendKeepalive.Del()
+ peer.timers.retransmitHandshake.Mod(RekeyTimeout + time.Millisecond*time.Duration(rand.Int31n(RekeyTimeoutJitterMaxMs)))
+ }
+}
- // clear all queued packets and stop keep-alive
+/* Should be called after a handshake response message is received and processed or when getting key confirmation via the first data message. */
+func (peer *Peer) timersHandshakeComplete() {
+ if peer.timersActive() {
+ peer.timers.retransmitHandshake.Del()
+ }
+ peer.timers.handshakeAttempts = 0
+ peer.timers.sentLastMinuteHandshake = false
+ atomic.StoreInt64(&peer.stats.lastHandshakeNano, time.Now().UnixNano())
+}
- logInfo.Println(peer, ": Handshake negotiation timed-out")
+/* Should be called after an ephemeral key is created, which is before sending a handshake response or after receiving a handshake response. */
+func (peer *Peer) timersSessionDerived() {
+ if peer.timersActive() {
+ peer.timers.zeroKeyMaterial.Mod(RejectAfterTime * 3)
+ }
+}
- peer.flushNonceQueue()
- peer.event.flushNonceQueue.Fire()
+/* Should be called before a packet with authentication -- data, keepalive, either handshake -- is sent, or after one is received. */
+func (peer *Peer) timersAnyAuthenticatedPacketTraversal() {
+ if peer.persistentKeepaliveInterval > 0 && peer.timersActive() {
+ peer.timers.persistentKeepalive.Mod(time.Duration(peer.persistentKeepaliveInterval) * time.Second)
+ }
+}
- // renable further handshakes
+func (peer *Peer) timersInit() {
+ peer.timers.retransmitHandshake = peer.NewTimer(expiredRetransmitHandshake)
+ peer.timers.sendKeepalive = peer.NewTimer(expiredSendKeepalive)
+ peer.timers.newHandshake = peer.NewTimer(expiredNewHandshake)
+ peer.timers.zeroKeyMaterial = peer.NewTimer(expiredZeroKeyMaterial)
+ peer.timers.persistentKeepalive = peer.NewTimer(expiredPersistentKeepalive)
+ peer.timers.handshakeAttempts = 0
+ peer.timers.sentLastMinuteHandshake = false
+ peer.timers.needAnotherKeepalive = false
+ peer.timers.lastSentHandshake = time.Now().Add(-(RekeyTimeout + time.Second))
+}
- peer.event.handshakeBegin.Clear()
- enableHandshake = true
- }
- }
+func (peer *Peer) timersStop() {
+ peer.timers.retransmitHandshake.Del()
+ peer.timers.sendKeepalive.Del()
+ peer.timers.newHandshake.Del()
+ peer.timers.zeroKeyMaterial.Del()
+ peer.timers.persistentKeepalive.Del()
}