summaryrefslogtreecommitdiffhomepage
path: root/src/timers.go
diff options
context:
space:
mode:
authorMathias Hall-Andersen <mathias@hall-andersen.dk>2017-07-08 23:51:26 +0200
committerMathias Hall-Andersen <mathias@hall-andersen.dk>2017-07-08 23:51:26 +0200
commit4ad62aaa6aa269f08c0fdc9c139e6d5417e21746 (patch)
tree2105f65bc61b0fb842ac44c11a81150a8bb9a909 /src/timers.go
parent5c1ccbddf0c6fdfd98cb3204c1cd4726862855f2 (diff)
Improved timer state machine
Diffstat (limited to 'src/timers.go')
-rw-r--r--src/timers.go303
1 files changed, 303 insertions, 0 deletions
diff --git a/src/timers.go b/src/timers.go
new file mode 100644
index 0000000..26926c2
--- /dev/null
+++ b/src/timers.go
@@ -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)
+ }
+}