Handle the decodeAuthMessage error case separatly and log to trace (#498)

* Handle the decodeAuthMessage error case separatly and log to trace

Garbage data on the TCP port (e.g. from port scanners) would
cause lots of error log messages, so log this to trace and get rid
of a (little) bit of exception usage in the process.

* Remove usage of result var in rlpxAccept and rlpxConnect

* Discv4: Add ENRRequest & ENRResponse msgs to avoid fails on these

Fix #499
These messages are not implemented yet however, but just ignored.
This commit is contained in:
Kim De Mey 2022-04-04 22:31:09 +02:00 committed by GitHub
parent 41b8588ade
commit 6d4b1f4fe1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 64 deletions

View File

@ -95,12 +95,11 @@ proc `xor`[N: static int](a, b: array[N, byte]): array[N, byte] =
proc mapErrTo[T, E](r: Result[T, E], v: static AuthError): AuthResult[T] = proc mapErrTo[T, E](r: Result[T, E], v: static AuthError): AuthResult[T] =
r.mapErr(proc (e: E): AuthError = v) r.mapErr(proc (e: E): AuthError = v)
proc tryInit*( proc init*(
T: type Handshake, rng: var BrHmacDrbgContext, host: KeyPair, T: type Handshake, rng: var BrHmacDrbgContext, host: KeyPair,
flags: set[HandshakeFlag] = {Initiator}, flags: set[HandshakeFlag] = {Initiator},
version: uint8 = SupportedRlpxVersion): AuthResult[T] = version: uint8 = SupportedRlpxVersion): T =
## Create new `Handshake` object. ## Create new `Handshake` object.
var var
initiatorNonce: Nonce initiatorNonce: Nonce
responderNonce: Nonce responderNonce: Nonce
@ -114,7 +113,7 @@ proc tryInit*(
expectedLength = AuthMessageV4Length expectedLength = AuthMessageV4Length
brHmacDrbgGenerate(rng, responderNonce) brHmacDrbgGenerate(rng, responderNonce)
return ok(T( return T(
version: version, version: version,
flags: flags, flags: flags,
host: host, host: host,
@ -122,7 +121,7 @@ proc tryInit*(
initiatorNonce: initiatorNonce, initiatorNonce: initiatorNonce,
responderNonce: responderNonce, responderNonce: responderNonce,
expectedLength: expectedLength expectedLength: expectedLength
)) )
proc authMessagePreEIP8(h: var Handshake, proc authMessagePreEIP8(h: var Handshake,
rng: var BrHmacDrbgContext, rng: var BrHmacDrbgContext,

View File

@ -44,12 +44,14 @@ type
cmdPong = 2 cmdPong = 2
cmdFindNode = 3 cmdFindNode = 3
cmdNeighbours = 4 cmdNeighbours = 4
cmdENRRequest = 5
cmdENRResponse = 6
DiscProtocolError* = object of CatchableError DiscProtocolError* = object of CatchableError
DiscResult*[T] = Result[T, cstring] DiscResult*[T] = Result[T, cstring]
const MinListLen: array[CommandId, int] = [4, 3, 2, 2] const MinListLen: array[CommandId, int] = [4, 3, 2, 2, 1, 2]
proc append*(w: var RlpWriter, a: IpAddress) = proc append*(w: var RlpWriter, a: IpAddress) =
case a.family case a.family
@ -267,6 +269,9 @@ proc receive*(d: DiscoveryProtocol, a: Address, msg: openArray[byte])
d.recvNeighbours(node, payload) d.recvNeighbours(node, payload)
of cmdFindNode: of cmdFindNode:
d.recvFindNode(node, payload) d.recvFindNode(node, payload)
of cmdENRRequest, cmdENRResponse:
# TODO: Implement EIP-868
discard
else: else:
trace "Received msg already expired", cmdId, a trace "Received msg already expired", cmdId, a
else: else:

View File

@ -1212,22 +1212,19 @@ template baseProtocolVersion(peer: Peer): uint =
proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} = proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
initTracing(devp2pInfo, node.protocols) initTracing(devp2pInfo, node.protocols)
new result let peer = Peer(remote: remote, network: node)
result.network = node
result.remote = remote
let ta = initTAddress(remote.node.address.ip, remote.node.address.tcpPort) let ta = initTAddress(remote.node.address.ip, remote.node.address.tcpPort)
var ok = false var ok = false
try: try:
result.transport = await connect(ta) peer.transport = await connect(ta)
var handshake = Handshake.tryInit( var handshake = Handshake.init(
node.rng[], node.keys, {Initiator, EIP8}, node.baseProtocolVersion).tryGet() node.rng[], node.keys, {Initiator, EIP8}, node.baseProtocolVersion)
var authMsg: array[AuthMessageMaxEIP8, byte] var authMsg: array[AuthMessageMaxEIP8, byte]
var authMsgLen = 0 var authMsgLen = 0
authMessage( authMessage(
handshake, node.rng[], remote.node.pubkey, authMsg, authMsgLen).tryGet() handshake, node.rng[], remote.node.pubkey, authMsg, authMsgLen).tryGet()
var res = await result.transport.write(addr authMsg[0], authMsgLen) var res = await peer.transport.write(addr authMsg[0], authMsgLen)
if res != authMsgLen: if res != authMsgLen:
raisePeerDisconnected("Unexpected disconnect while authenticating", raisePeerDisconnected("Unexpected disconnect while authenticating",
TcpError) TcpError)
@ -1236,33 +1233,41 @@ proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
var ackMsg = newSeqOfCap[byte](1024) var ackMsg = newSeqOfCap[byte](1024)
ackMsg.setLen(initialSize) ackMsg.setLen(initialSize)
await result.transport.readExactly(addr ackMsg[0], len(ackMsg)) # TODO: Should we not set some timeouts on these `readExactly`s?
await peer.transport.readExactly(addr ackMsg[0], len(ackMsg))
var ret = handshake.decodeAckMessage(ackMsg) var ret = handshake.decodeAckMessage(ackMsg)
if ret.isErr and ret.error == AuthError.IncompleteError: if ret.isErr and ret.error == AuthError.IncompleteError:
ackMsg.setLen(handshake.expectedLength) ackMsg.setLen(handshake.expectedLength)
await result.transport.readExactly(addr ackMsg[initialSize], await peer.transport.readExactly(addr ackMsg[initialSize],
len(ackMsg) - initialSize) len(ackMsg) - initialSize)
ret = handshake.decodeAckMessage(ackMsg) ret = handshake.decodeAckMessage(ackMsg)
ret.tryGet() # for the raise!
node.checkSnappySupport(handshake, result) if ret.isErr():
initSecretState(handshake, ^authMsg, ackMsg, result) debug "rlpxConnect handshake error", error = ret.error
if not isNil(peer.transport):
peer.transport.close()
return nil
ret.get()
node.checkSnappySupport(handshake, peer)
initSecretState(handshake, ^authMsg, ackMsg, peer)
# if handshake.remoteHPubkey != remote.node.pubKey: # if handshake.remoteHPubkey != remote.node.pubKey:
# raise newException(Exception, "Remote pubkey is wrong") # raise newException(Exception, "Remote pubkey is wrong")
logConnectedPeer result logConnectedPeer peer
var sendHelloFut = result.hello( var sendHelloFut = peer.hello(
handshake.getVersion(), handshake.getVersion(),
node.clientId, node.clientId,
node.capabilities, node.capabilities,
uint(node.address.tcpPort), uint(node.address.tcpPort),
node.keys.pubkey.toRaw()) node.keys.pubkey.toRaw())
var response = await result.handshakeImpl( var response = await peer.handshakeImpl(
sendHelloFut, sendHelloFut,
result.waitSingleMsg(DevP2P.hello), peer.waitSingleMsg(DevP2P.hello),
10.seconds) 10.seconds)
if not validatePubKeyInHello(response, remote.node.pubkey): if not validatePubKeyInHello(response, remote.node.pubkey):
@ -1271,7 +1276,7 @@ proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
trace "DevP2P handshake completed", peer = remote, trace "DevP2P handshake completed", peer = remote,
clientId = response.clientId clientId = response.clientId
await postHelloSteps(result, response) await postHelloSteps(peer, response)
ok = true ok = true
trace "Peer fully connected", peer = remote, clientId = response.clientId trace "Peer fully connected", peer = remote, clientId = response.clientId
except PeerDisconnected as e: except PeerDisconnected as e:
@ -1296,39 +1301,47 @@ proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
err = e.msg err = e.msg
if not ok: if not ok:
if not isNil(result.transport): if not isNil(peer.transport):
result.transport.close() peer.transport.close()
result = nil return nil
else:
return peer
proc rlpxAccept*(node: EthereumNode, proc rlpxAccept*(node: EthereumNode,
transport: StreamTransport): Future[Peer] {.async.} = transport: StreamTransport): Future[Peer] {.async.} =
initTracing(devp2pInfo, node.protocols) initTracing(devp2pInfo, node.protocols)
new result let peer = Peer(transport: transport, network: node)
result.transport = transport var handshake = Handshake.init(node.rng[], node.keys, {auth.Responder})
result.network = node
var handshake =
Handshake.tryInit(node.rng[], node.keys, {auth.Responder}).tryGet
var ok = false var ok = false
try: try:
let initialSize = handshake.expectedLength let initialSize = handshake.expectedLength
var authMsg = newSeqOfCap[byte](1024) var authMsg = newSeqOfCap[byte](1024)
authMsg.setLen(initialSize) authMsg.setLen(initialSize)
# TODO: Should we not set some timeouts on these `readExactly`s?
await transport.readExactly(addr authMsg[0], len(authMsg)) await transport.readExactly(addr authMsg[0], len(authMsg))
var ret = handshake.decodeAuthMessage(authMsg) var ret = handshake.decodeAuthMessage(authMsg)
if ret.isErr and ret.error == AuthError.IncompleteError: if ret.isErr and ret.error == AuthError.IncompleteError:
# Eip8 auth message is likely # Eip8 auth message is possible, but not likely
authMsg.setLen(handshake.expectedLength) authMsg.setLen(handshake.expectedLength)
await transport.readExactly(addr authMsg[initialSize], await transport.readExactly(addr authMsg[initialSize],
len(authMsg) - initialSize) len(authMsg) - initialSize)
ret = handshake.decodeAuthMessage(authMsg) ret = handshake.decodeAuthMessage(authMsg)
ret.tryGet() # for the raise!
node.checkSnappySupport(handshake, result) if ret.isErr():
handshake.version = uint8(result.baseProtocolVersion) # It is likely that errors on the handshake Auth is just garbage arriving
# on the TCP port as it is the first data on the incoming connection,
# hence log them as trace.
trace "rlpxAccept handshake error", error = ret.error
if not isNil(peer.transport):
peer.transport.close()
return nil
ret.get()
node.checkSnappySupport(handshake, peer)
handshake.version = uint8(peer.baseProtocolVersion)
var ackMsg: array[AckMessageMaxEIP8, byte] var ackMsg: array[AckMessageMaxEIP8, byte]
var ackMsgLen: int var ackMsgLen: int
@ -1338,22 +1351,22 @@ proc rlpxAccept*(node: EthereumNode,
raisePeerDisconnected("Unexpected disconnect while authenticating", raisePeerDisconnected("Unexpected disconnect while authenticating",
TcpError) TcpError)
initSecretState(handshake, authMsg, ^ackMsg, result) initSecretState(handshake, authMsg, ^ackMsg, peer)
let listenPort = transport.localAddress().port let listenPort = transport.localAddress().port
logAcceptedPeer result logAcceptedPeer peer
var sendHelloFut = result.hello( var sendHelloFut = peer.hello(
result.baseProtocolVersion, peer.baseProtocolVersion,
node.clientId, node.clientId,
node.capabilities, node.capabilities,
listenPort.uint, listenPort.uint,
node.keys.pubkey.toRaw()) node.keys.pubkey.toRaw())
var response = await result.handshakeImpl( var response = await peer.handshakeImpl(
sendHelloFut, sendHelloFut,
result.waitSingleMsg(DevP2P.hello), peer.waitSingleMsg(DevP2P.hello),
10.seconds) 10.seconds)
trace "Received Hello", version=response.version, id=response.clientId trace "Received Hello", version=response.version, id=response.clientId
@ -1364,36 +1377,36 @@ proc rlpxAccept*(node: EthereumNode,
let remote = transport.remoteAddress() let remote = transport.remoteAddress()
let address = Address(ip: remote.address, tcpPort: remote.port, let address = Address(ip: remote.address, tcpPort: remote.port,
udpPort: remote.port) udpPort: remote.port)
result.remote = newNode( peer.remote = newNode(
ENode(pubkey: handshake.remoteHPubkey, address: address)) ENode(pubkey: handshake.remoteHPubkey, address: address))
trace "devp2p handshake completed", peer = result.remote, trace "devp2p handshake completed", peer = peer.remote,
clientId = response.clientId clientId = response.clientId
# In case there is an outgoing connection started with this peer we give # In case there is an outgoing connection started with this peer we give
# precedence to that one and we disconnect here with `AlreadyConnected` # precedence to that one and we disconnect here with `AlreadyConnected`
if result.remote in node.peerPool.connectedNodes or if peer.remote in node.peerPool.connectedNodes or
result.remote in node.peerPool.connectingNodes: peer.remote in node.peerPool.connectingNodes:
trace "Duplicate connection in rlpxAccept" trace "Duplicate connection in rlpxAccept"
raisePeerDisconnected("Peer already connecting or connected", raisePeerDisconnected("Peer already connecting or connected",
AlreadyConnected) AlreadyConnected)
node.peerPool.connectingNodes.incl(result.remote) node.peerPool.connectingNodes.incl(peer.remote)
await postHelloSteps(result, response) await postHelloSteps(peer, response)
ok = true ok = true
trace "Peer fully connected", peer = result.remote, clientId = response.clientId trace "Peer fully connected", peer = peer.remote, clientId = response.clientId
except PeerDisconnected as e: except PeerDisconnected as e:
case e.reason case e.reason
of AlreadyConnected, TooManyPeers, MessageTimeout: of AlreadyConnected, TooManyPeers, MessageTimeout:
trace "Disconnect during rlpxAccept", reason = e.reason, peer = result.remote trace "Disconnect during rlpxAccept", reason = e.reason, peer = peer.remote
else: else:
debug "Unexpected disconnect during rlpxAccept", reason = e.reason, debug "Unexpected disconnect during rlpxAccept", reason = e.reason,
msg = e.msg, peer = result.remote msg = e.msg, peer = peer.remote
except TransportIncompleteError: except TransportIncompleteError:
trace "Connection dropped in rlpxAccept", remote = result.remote trace "Connection dropped in rlpxAccept", remote = peer.remote
except UselessPeerError: except UselessPeerError:
trace "Disconnecting useless peer", peer = result.remote trace "Disconnecting useless peer", peer = peer.remote
except RlpTypeMismatch: except RlpTypeMismatch:
# Some peers report capabilities with names longer than 3 chars. We ignore # Some peers report capabilities with names longer than 3 chars. We ignore
# those for now. Maybe we should allow this though. # those for now. Maybe we should allow this though.
@ -1404,9 +1417,11 @@ proc rlpxAccept*(node: EthereumNode,
error "Unexpected exception in rlpxAccept", exc = e.name, err = e.msg error "Unexpected exception in rlpxAccept", exc = e.name, err = e.msg
if not ok: if not ok:
if not isNil(result.transport): if not isNil(peer.transport):
result.transport.close() peer.transport.close()
result = nil return nil
else:
return peer
when isMainModule: when isMainModule:

View File

@ -221,7 +221,7 @@ suite "Ethereum P2P handshake test suite":
proc newTestHandshake(flags: set[HandshakeFlag]): Handshake = proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
if Initiator in flags: if Initiator in flags:
let pk = PrivateKey.fromHex(testValue("initiator_private_key"))[] let pk = PrivateKey.fromHex(testValue("initiator_private_key"))[]
result = Handshake.tryInit(rng[], pk.toKeyPair(), flags)[] result = Handshake.init(rng[], pk.toKeyPair(), flags)
let epki = testValue("initiator_ephemeral_private_key") let epki = testValue("initiator_ephemeral_private_key")
result.ephemeral = PrivateKey.fromHex(epki)[].toKeyPair() result.ephemeral = PrivateKey.fromHex(epki)[].toKeyPair()
@ -229,7 +229,7 @@ suite "Ethereum P2P handshake test suite":
result.initiatorNonce[0..^1] = nonce[0..^1] result.initiatorNonce[0..^1] = nonce[0..^1]
elif Responder in flags: elif Responder in flags:
let pk = PrivateKey.fromHex(testValue("receiver_private_key"))[] let pk = PrivateKey.fromHex(testValue("receiver_private_key"))[]
result = Handshake.tryInit(rng[], pk.toKeyPair(), flags)[] result = Handshake.init(rng[], pk.toKeyPair(), flags)
let epkr = testValue("receiver_ephemeral_private_key") let epkr = testValue("receiver_ephemeral_private_key")
result.ephemeral = PrivateKey.fromHex(epkr)[].toKeyPair() result.ephemeral = PrivateKey.fromHex(epkr)[].toKeyPair()
let nonce = fromHex(stripSpaces(testValue("receiver_nonce"))) let nonce = fromHex(stripSpaces(testValue("receiver_nonce")))
@ -333,7 +333,7 @@ suite "Ethereum P2P handshake test suite":
proc newTestHandshake(flags: set[HandshakeFlag]): Handshake = proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
if Initiator in flags: if Initiator in flags:
let pk = PrivateKey.fromHex(testE8Value("initiator_private_key"))[] let pk = PrivateKey.fromHex(testE8Value("initiator_private_key"))[]
result = Handshake.tryInit(rng[], pk.toKeyPair(), flags)[] result = Handshake.init(rng[], pk.toKeyPair(), flags)
let esec = testE8Value("initiator_ephemeral_private_key") let esec = testE8Value("initiator_ephemeral_private_key")
result.ephemeral = PrivateKey.fromHex(esec)[].toKeyPair() result.ephemeral = PrivateKey.fromHex(esec)[].toKeyPair()
@ -341,7 +341,7 @@ suite "Ethereum P2P handshake test suite":
result.initiatorNonce[0..^1] = nonce[0..^1] result.initiatorNonce[0..^1] = nonce[0..^1]
elif Responder in flags: elif Responder in flags:
let pk = PrivateKey.fromHex(testE8Value("receiver_private_key"))[] let pk = PrivateKey.fromHex(testE8Value("receiver_private_key"))[]
result = Handshake.tryInit(rng[], pk.toKeyPair(), flags)[] result = Handshake.init(rng[], pk.toKeyPair(), flags)
let esec = testE8Value("receiver_ephemeral_private_key") let esec = testE8Value("receiver_ephemeral_private_key")
result.ephemeral = PrivateKey.fromHex(esec)[].toKeyPair() result.ephemeral = PrivateKey.fromHex(esec)[].toKeyPair()

View File

@ -95,14 +95,14 @@ suite "Ethereum RLPx encryption/decryption test suite":
proc newTestHandshake(flags: set[HandshakeFlag]): Handshake = proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
if Initiator in flags: if Initiator in flags:
let pk = PrivateKey.fromHex(testValue("initiator_private_key"))[] let pk = PrivateKey.fromHex(testValue("initiator_private_key"))[]
result = Handshake.tryInit(rng[], pk.toKeyPair(), flags)[] result = Handshake.init(rng[], pk.toKeyPair(), flags)
let epki = testValue("initiator_ephemeral_private_key") let epki = testValue("initiator_ephemeral_private_key")
result.ephemeral = PrivateKey.fromHex(epki)[].toKeyPair() result.ephemeral = PrivateKey.fromHex(epki)[].toKeyPair()
let nonce = fromHex(stripSpaces(testValue("initiator_nonce"))) let nonce = fromHex(stripSpaces(testValue("initiator_nonce")))
result.initiatorNonce[0..^1] = nonce[0..^1] result.initiatorNonce[0..^1] = nonce[0..^1]
elif Responder in flags: elif Responder in flags:
let pk = PrivateKey.fromHex(testValue("receiver_private_key"))[] let pk = PrivateKey.fromHex(testValue("receiver_private_key"))[]
result = Handshake.tryInit(rng[], pk.toKeyPair(), flags)[] result = Handshake.init(rng[], pk.toKeyPair(), flags)
let epkr = testValue("receiver_ephemeral_private_key") let epkr = testValue("receiver_ephemeral_private_key")
result.ephemeral = PrivateKey.fromHex(epkr)[].toKeyPair() result.ephemeral = PrivateKey.fromHex(epkr)[].toKeyPair()
let nonce = fromHex(stripSpaces(testValue("receiver_nonce"))) let nonce = fromHex(stripSpaces(testValue("receiver_nonce")))