Fix: queue messages when there is no encryption key (#83)

* encodeMessagePacket: expose haskey

encodeMessagePacket checks for session and behaves differently
based on that. Exposing this difference in behavior.

Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com>

* improve tracing of message exchange

run e.g. as
```
nim c -r -d:debug -d:chronicles_enabled=on -d:chronicles_log_level=TRACE -d:chronicles_sinks=textlines[nocolors,stdout] tests/dht/test_providers.nim >err
```

Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com>

* add debug on Handshake timeour

Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com>

* queue messages during handshake and send later

If a handshake was already in progress, messages were dropped.
Instead of this, it is better to queue these and send as soon
as the handshake is finished and thus the encryption key is known.

Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com>

* rename handshakeInProgress to keyexchangeInProgress

Handshake is also a name of a message, which makes previous
name less clear.

Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com>

* keyexchangeInProgress: do not remove on handshake received

This is the wrong direction, not needed

Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com>

---------

Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com>
This commit is contained in:
Csaba Kiraly 2023-11-17 20:18:48 +01:00 committed by GitHub
parent 60dc4e764c
commit 66116b9bf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 21 deletions

View File

@ -209,8 +209,9 @@ proc encodeStaticHeader*(flag: Flag, nonce: AESGCMNonce, authSize: int):
proc encodeMessagePacket*(rng: var HmacDrbgContext, c: var Codec, proc encodeMessagePacket*(rng: var HmacDrbgContext, c: var Codec,
toId: NodeId, toAddr: Address, message: openArray[byte]): toId: NodeId, toAddr: Address, message: openArray[byte]):
(seq[byte], AESGCMNonce) = (seq[byte], AESGCMNonce, bool) =
var nonce: AESGCMNonce var nonce: AESGCMNonce
var haskey: bool
hmacDrbgGenerate(rng, nonce) # Random AESGCM nonce hmacDrbgGenerate(rng, nonce) # Random AESGCM nonce
var iv: array[ivSize, byte] var iv: array[ivSize, byte]
hmacDrbgGenerate(rng, iv) # Random IV hmacDrbgGenerate(rng, iv) # Random IV
@ -228,6 +229,7 @@ proc encodeMessagePacket*(rng: var HmacDrbgContext, c: var Codec,
var messageEncrypted: seq[byte] var messageEncrypted: seq[byte]
var initiatorKey, recipientKey: AesKey var initiatorKey, recipientKey: AesKey
if c.sessions.load(toId, toAddr, recipientKey, initiatorKey): if c.sessions.load(toId, toAddr, recipientKey, initiatorKey):
haskey = true
messageEncrypted = encryptGCM(initiatorKey, nonce, message, @iv & header) messageEncrypted = encryptGCM(initiatorKey, nonce, message, @iv & header)
discovery_session_lru_cache_hits.inc() discovery_session_lru_cache_hits.inc()
else: else:
@ -238,6 +240,7 @@ proc encodeMessagePacket*(rng: var HmacDrbgContext, c: var Codec,
# message. 16 bytes for the gcm tag and 4 bytes for ping with requestId of # message. 16 bytes for the gcm tag and 4 bytes for ping with requestId of
# 1 byte (e.g "01c20101"). Could increase to 27 for 8 bytes requestId in # 1 byte (e.g "01c20101"). Could increase to 27 for 8 bytes requestId in
# case this must not look like a random packet. # case this must not look like a random packet.
haskey = false
var randomData: array[gcmTagSize + 4, byte] var randomData: array[gcmTagSize + 4, byte]
hmacDrbgGenerate(rng, randomData) hmacDrbgGenerate(rng, randomData)
messageEncrypted.add(randomData) messageEncrypted.add(randomData)
@ -250,7 +253,7 @@ proc encodeMessagePacket*(rng: var HmacDrbgContext, c: var Codec,
packet.add(maskedHeader) packet.add(maskedHeader)
packet.add(messageEncrypted) packet.add(messageEncrypted)
return (packet, nonce) return (packet, nonce, haskey)
proc encodeWhoareyouPacket*(rng: var HmacDrbgContext, c: var Codec, proc encodeWhoareyouPacket*(rng: var HmacDrbgContext, c: var Codec,
toId: NodeId, toAddr: Address, requestNonce: AESGCMNonce, recordSeq: uint64, toId: NodeId, toAddr: Address, requestNonce: AESGCMNonce, recordSeq: uint64,

View File

@ -6,7 +6,7 @@
# Everything below the handling of ordinary messages # Everything below the handling of ordinary messages
import import
std/[tables, options], std/[tables, options, sets],
bearssl/rand, bearssl/rand,
chronos, chronos,
chronicles, chronicles,
@ -26,6 +26,8 @@ type
bindAddress: Address ## UDP binding address bindAddress: Address ## UDP binding address
transp: DatagramTransport transp: DatagramTransport
pendingRequests: Table[AESGCMNonce, PendingRequest] pendingRequests: Table[AESGCMNonce, PendingRequest]
keyexchangeInProgress: HashSet[NodeId]
pendingRequestsByNode: Table[NodeId, seq[seq[byte]]]
codec*: Codec codec*: Codec
rng: ref HmacDrbgContext rng: ref HmacDrbgContext
@ -34,6 +36,7 @@ type
message: seq[byte] message: seq[byte]
proc sendToA(t: Transport, a: Address, data: seq[byte]) = proc sendToA(t: Transport, a: Address, data: seq[byte]) =
trace "Send packet", myport = t.bindAddress.port, address = a
let ta = initTAddress(a.ip, a.port) let ta = initTAddress(a.ip, a.port)
let f = t.transp.sendTo(ta, data) let f = t.transp.sendTo(ta, data)
f.callback = proc(data: pointer) {.gcsafe.} = f.callback = proc(data: pointer) {.gcsafe.} =
@ -55,7 +58,7 @@ proc send(t: Transport, n: Node, data: seq[byte]) =
t.sendToA(n.address.get(), data) t.sendToA(n.address.get(), data)
proc sendMessage*(t: Transport, toId: NodeId, toAddr: Address, message: seq[byte]) = proc sendMessage*(t: Transport, toId: NodeId, toAddr: Address, message: seq[byte]) =
let (data, _) = encodeMessagePacket(t.rng[], t.codec, toId, toAddr, let (data, _, _) = encodeMessagePacket(t.rng[], t.codec, toId, toAddr,
message) message)
t.sendToA(toAddr, data) t.sendToA(toAddr, data)
@ -73,11 +76,30 @@ proc registerRequest(t: Transport, n: Node, message: seq[byte],
proc sendMessage*(t: Transport, toNode: Node, message: seq[byte]) = proc sendMessage*(t: Transport, toNode: Node, message: seq[byte]) =
doAssert(toNode.address.isSome()) doAssert(toNode.address.isSome())
let address = toNode.address.get() let address = toNode.address.get()
let (data, nonce) = encodeMessagePacket(t.rng[], t.codec, let (data, nonce, haskey) = encodeMessagePacket(t.rng[], t.codec,
toNode.id, address, message) toNode.id, address, message)
t.registerRequest(toNode, message, nonce) if haskey:
t.send(toNode, data) trace "Send message: has key", myport = t.bindAddress.port , dstId = toNode
t.registerRequest(toNode, message, nonce)
t.send(toNode, data)
else:
# we don't have an encryption key for this target, so we should initiate keyexchange
if not (toNode.id in t.keyexchangeInProgress):
trace "Send message: send random to trigger Whoareyou", myport = t.bindAddress.port , dstId = toNode
t.registerRequest(toNode, message, nonce)
t.send(toNode, data)
t.keyexchangeInProgress.incl(toNode.id)
trace "keyexchangeInProgress added", myport = t.bindAddress.port , dstId = toNode
sleepAsync(responseTimeout).addCallback() do(data: pointer):
t.keyexchangeInProgress.excl(toNode.id)
trace "keyexchangeInProgress removed (timeout)", myport = t.bindAddress.port , dstId = toNode
else:
# delay sending this message until whoareyou is received and handshake is sent
# have to reencode once keys are clear
t.pendingRequestsByNode.mgetOrPut(toNode.id, newSeq[seq[byte]]()).add(message)
trace "Send message: Node with this id already has ongoing keyexchage, delaying packet",
myport = t.bindAddress.port , dstId = toNode, qlen=t.pendingRequestsByNode[toNode.id].len
proc sendWhoareyou(t: Transport, toId: NodeId, a: Address, proc sendWhoareyou(t: Transport, toId: NodeId, a: Address,
requestNonce: AESGCMNonce, node: Option[Node]) = requestNonce: AESGCMNonce, node: Option[Node]) =
@ -94,13 +116,28 @@ proc sendWhoareyou(t: Transport, toId: NodeId, a: Address,
sleepAsync(handshakeTimeout).addCallback() do(data: pointer): sleepAsync(handshakeTimeout).addCallback() do(data: pointer):
# TODO: should we still provide cancellation in case handshake completes # TODO: should we still provide cancellation in case handshake completes
# correctly? # correctly?
t.codec.handshakes.del(key) if t.codec.hasHandshake(key):
debug "Handshake timeout", myport = t.bindAddress.port , dstId = toId, address = a
t.codec.handshakes.del(key)
trace "Send whoareyou", dstId = toId, address = a trace "Send whoareyou", dstId = toId, address = a
t.sendToA(a, data) t.sendToA(a, data)
else: else:
debug "Node with this id already has ongoing handshake, ignoring packet" # TODO: is this reasonable to drop it? Should we allow a mini-queue here?
# Queue should be on sender side, as this is random encoded!
debug "Node with this id already has ongoing handshake, queuing packet", myport = t.bindAddress.port , dstId = toId, address = a
proc sendPending(t:Transport, toNode: Node):
Future[void] {.async.} =
if t.pendingRequestsByNode.hasKey(toNode.id):
trace "Found pending request", myport = t.bindAddress.port, src = toNode, len = t.pendingRequestsByNode[toNode.id].len
for message in t.pendingRequestsByNode[toNode.id]:
trace "Sending pending packet", myport = t.bindAddress.port, dstId = toNode.id
let address = toNode.address.get()
let (data, nonce, haskey) = encodeMessagePacket(t.rng[], t.codec, toNode.id, address, message)
t.registerRequest(toNode, message, nonce)
t.send(toNode, data)
t.pendingRequestsByNode.del(toNode.id)
proc receive*(t: Transport, a: Address, packet: openArray[byte]) = proc receive*(t: Transport, a: Address, packet: openArray[byte]) =
let decoded = t.codec.decodePacket(a, packet) let decoded = t.codec.decodePacket(a, packet)
if decoded.isOk: if decoded.isOk:
@ -109,17 +146,23 @@ proc receive*(t: Transport, a: Address, packet: openArray[byte]) =
of OrdinaryMessage: of OrdinaryMessage:
if packet.messageOpt.isSome(): if packet.messageOpt.isSome():
let message = packet.messageOpt.get() let message = packet.messageOpt.get()
trace "Received message packet", srcId = packet.srcId, address = a, trace "Received message packet", myport = t.bindAddress.port, srcId = packet.srcId, address = a,
kind = message.kind, p = $packet kind = message.kind, p = $packet
t.client.handleMessage(packet.srcId, a, message) t.client.handleMessage(packet.srcId, a, message)
else: else:
trace "Not decryptable message packet received", trace "Not decryptable message packet received", myport = t.bindAddress.port,
srcId = packet.srcId, address = a srcId = packet.srcId, address = a
# If we already have a keyexchange in progress, we have a case of simultaneous cross-connect.
# We could try to decide here which should go on, but since we are on top of UDP, a more robust
# choice is to answer here and resolve conflicts in the next stage (reception of Whoareyou), or
# even later (reception of Handshake).
if packet.srcId in t.keyexchangeInProgress:
trace "cross-connect detected, still sending Whoareyou"
t.sendWhoareyou(packet.srcId, a, packet.requestNonce, t.sendWhoareyou(packet.srcId, a, packet.requestNonce,
t.client.getNode(packet.srcId)) t.client.getNode(packet.srcId))
of Flag.Whoareyou: of Flag.Whoareyou:
trace "Received whoareyou packet", address = a trace "Received whoareyou packet", myport = t.bindAddress.port, address = a
var pr: PendingRequest var pr: PendingRequest
if t.pendingRequests.take(packet.whoareyou.requestNonce, pr): if t.pendingRequests.take(packet.whoareyou.requestNonce, pr):
let toNode = pr.node let toNode = pr.node
@ -136,12 +179,17 @@ proc receive*(t: Transport, a: Address, packet: openArray[byte]) =
toNode.pubkey toNode.pubkey
).expect("Valid handshake packet to encode") ).expect("Valid handshake packet to encode")
trace "Send handshake message packet", dstId = toNode.id, address trace "Send handshake message packet", myport = t.bindAddress.port, dstId = toNode.id, address
t.send(toNode, data) t.send(toNode, data)
# keyexchange ready, we can send queued packets
t.keyexchangeInProgress.excl(toNode.id)
trace "keyexchangeInProgress removed (finished)", myport = t.bindAddress.port, dstId = toNode.id, address
discard t.sendPending(toNode)
else: else:
debug "Timed out or unrequested whoareyou packet", address = a debug "Timed out or unrequested whoareyou packet", address = a
of HandshakeMessage: of HandshakeMessage:
trace "Received handshake message packet", srcId = packet.srcIdHs, trace "Received handshake message packet", myport = t.bindAddress.port, srcId = packet.srcIdHs,
address = a, kind = packet.message.kind address = a, kind = packet.message.kind
t.client.handleMessage(packet.srcIdHs, a, packet.message) t.client.handleMessage(packet.srcIdHs, a, packet.message)
# For a handshake message it is possible that we received an newer SPR. # For a handshake message it is possible that we received an newer SPR.
@ -157,9 +205,10 @@ proc receive*(t: Transport, a: Address, packet: openArray[byte]) =
# sending the 'whoareyou' message to. In that case, we can set 'seen' # sending the 'whoareyou' message to. In that case, we can set 'seen'
node.seen = true node.seen = true
if t.client.addNode(node): if t.client.addNode(node):
trace "Added new node to routing table after handshake", node trace "Added new node to routing table after handshake", node, tablesize=t.client.nodesDiscovered()
discard t.sendPending(node)
else: else:
trace "Packet decoding error", error = decoded.error, address = a trace "Packet decoding error", myport = t.bindAddress.port, error = decoded.error, address = a
proc processClient[T](transp: DatagramTransport, raddr: TransportAddress): proc processClient[T](transp: DatagramTransport, raddr: TransportAddress):
Future[void] {.async.} = Future[void] {.async.} =

View File

@ -629,7 +629,7 @@ suite "Discovery v5 Tests":
sendNode = newNode(enrRec).expect("Properly initialized record") sendNode = newNode(enrRec).expect("Properly initialized record")
var codec = Codec(localNode: sendNode, privKey: privKey, sessions: Sessions.init(5)) var codec = Codec(localNode: sendNode, privKey: privKey, sessions: Sessions.init(5))
let (packet, _) = encodeMessagePacket(rng[], codec, let (packet, _, _) = encodeMessagePacket(rng[], codec,
receiveNode.localNode.id, receiveNode.localNode.address.get(), @[]) receiveNode.localNode.id, receiveNode.localNode.address.get(), @[])
receiveNode.transport.receive(a, packet) receiveNode.transport.receive(a, packet)
@ -659,7 +659,7 @@ suite "Discovery v5 Tests":
var codec = Codec(localNode: sendNode, privKey: privKey, sessions: Sessions.init(5)) var codec = Codec(localNode: sendNode, privKey: privKey, sessions: Sessions.init(5))
for i in 0 ..< 5: for i in 0 ..< 5:
let a = localAddress(20303 + i) let a = localAddress(20303 + i)
let (packet, _) = encodeMessagePacket(rng[], codec, let (packet, _, _) = encodeMessagePacket(rng[], codec,
receiveNode.localNode.id, receiveNode.localNode.address.get(), @[]) receiveNode.localNode.id, receiveNode.localNode.address.get(), @[])
receiveNode.transport.receive(a, packet) receiveNode.transport.receive(a, packet)
@ -691,7 +691,7 @@ suite "Discovery v5 Tests":
var firstRequestNonce: AESGCMNonce var firstRequestNonce: AESGCMNonce
for i in 0 ..< 5: for i in 0 ..< 5:
let (packet, requestNonce) = encodeMessagePacket(rng[], codec, let (packet, requestNonce, _) = encodeMessagePacket(rng[], codec,
receiveNode.localNode.id, receiveNode.localNode.address.get(), @[]) receiveNode.localNode.id, receiveNode.localNode.address.get(), @[])
receiveNode.transport.receive(a, packet) receiveNode.transport.receive(a, packet)
if i == 0: if i == 0:

View File

@ -526,7 +526,7 @@ suite "Discovery v5.1 Additional Encode/Decode":
reqId = RequestId.init(rng[]) reqId = RequestId.init(rng[])
message = encodeMessage(m, reqId) message = encodeMessage(m, reqId)
let (data, nonce) = encodeMessagePacket(rng[], codecA, nodeB.id, let (data, nonce, _) = encodeMessagePacket(rng[], codecA, nodeB.id,
nodeB.address.get(), message) nodeB.address.get(), message)
let decoded = codecB.decodePacket(nodeA.address.get(), data) let decoded = codecB.decodePacket(nodeA.address.get(), data)
@ -642,7 +642,7 @@ suite "Discovery v5.1 Additional Encode/Decode":
codecB.sessions.store(nodeA.id, nodeA.address.get(), secrets.initiatorKey, codecB.sessions.store(nodeA.id, nodeA.address.get(), secrets.initiatorKey,
secrets.recipientKey) secrets.recipientKey)
let (data, nonce) = encodeMessagePacket(rng[], codecA, nodeB.id, let (data, nonce, _) = encodeMessagePacket(rng[], codecA, nodeB.id,
nodeB.address.get(), message) nodeB.address.get(), message)
let decoded = codecB.decodePacket(nodeA.address.get(), data) let decoded = codecB.decodePacket(nodeA.address.get(), data)