Split upgrade flow (#507)

* splitting upgrade flow

* bring back master changes

* re-export `Upgrade`

* export public methods/procs in derived class

* style fixes
This commit is contained in:
Dmitriy Ryajov 2021-01-20 11:28:32 -06:00 committed by GitHub
parent 34e330353f
commit 96c01e5e69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 305 additions and 233 deletions

View File

@ -19,6 +19,7 @@ import chronos,
import stream/connection, import stream/connection,
transports/transport, transports/transport,
upgrademngrs/[upgrade, muxedupgrade],
multistream, multistream,
multiaddress, multiaddress,
protocols/protocol, protocols/protocol,
@ -31,7 +32,7 @@ import stream/connection,
peerid, peerid,
errors errors
export connmanager export connmanager, upgrade
logScope: logScope:
topics = "libp2p switch" topics = "libp2p switch"
@ -52,21 +53,16 @@ const
ConcurrentUpgrades* = 4 ConcurrentUpgrades* = 4
type type
UpgradeFailedError* = object of CatchableError
DialFailedError* = object of CatchableError DialFailedError* = object of CatchableError
Switch* = ref object of RootObj Switch* = ref object of RootObj
peerInfo*: PeerInfo peerInfo*: PeerInfo
connManager*: ConnManager connManager*: ConnManager
transports*: seq[Transport] transports*: seq[Transport]
protocols*: seq[LPProtocol]
muxers*: Table[string, MuxerProvider]
ms*: MultistreamSelect ms*: MultistreamSelect
identity*: Identify
streamHandler*: StreamHandler
secureManagers*: seq[Secure]
dialLock: Table[PeerID, AsyncLock] dialLock: Table[PeerID, AsyncLock]
acceptFuts: seq[Future[void]] acceptFuts: seq[Future[void]]
upgrade: Upgrade
proc addConnEventHandler*(s: Switch, proc addConnEventHandler*(s: Switch,
handler: ConnEventHandler, handler: ConnEventHandler,
@ -97,178 +93,9 @@ proc isConnected*(s: Switch, peerId: PeerID): bool =
peerId in s.connManager peerId in s.connManager
proc secure(s: Switch, conn: Connection): Future[Connection] {.async, gcsafe.} =
if s.secureManagers.len <= 0:
raise newException(UpgradeFailedError, "No secure managers registered!")
let codec = await s.ms.select(conn, s.secureManagers.mapIt(it.codec))
if codec.len == 0:
raise newException(UpgradeFailedError, "Unable to negotiate a secure channel!")
trace "Securing connection", conn, codec
let secureProtocol = s.secureManagers.filterIt(it.codec == codec)
# ms.select should deal with the correctness of this
# let's avoid duplicating checks but detect if it fails to do it properly
doAssert(secureProtocol.len > 0)
return await secureProtocol[0].secure(conn, true)
proc identify(s: Switch, conn: Connection) {.async, gcsafe.} =
## identify the connection
if (await s.ms.select(conn, s.identity.codec)):
let info = await s.identity.identify(conn, conn.peerInfo)
if info.pubKey.isNone and isNil(conn):
raise newException(UpgradeFailedError,
"no public key provided and no existing peer identity found")
if isNil(conn.peerInfo):
conn.peerInfo = PeerInfo.init(info.pubKey.get())
if info.addrs.len > 0:
conn.peerInfo.addrs = info.addrs
if info.agentVersion.isSome:
conn.peerInfo.agentVersion = info.agentVersion.get()
if info.protoVersion.isSome:
conn.peerInfo.protoVersion = info.protoVersion.get()
if info.protos.len > 0:
conn.peerInfo.protocols = info.protos
trace "identified remote peer", conn, peerInfo = shortLog(conn.peerInfo)
proc identify(s: Switch, muxer: Muxer) {.async, gcsafe.} =
# new stream for identify
var stream = await muxer.newStream()
if stream == nil:
return
try:
await s.identify(stream)
finally:
await stream.closeWithEOF()
proc mux(s: Switch, conn: Connection): Future[Muxer] {.async, gcsafe.} =
## mux incoming connection
trace "Muxing connection", conn
if s.muxers.len == 0:
warn "no muxers registered, skipping upgrade flow", conn
return
let muxerName = await s.ms.select(conn, toSeq(s.muxers.keys()))
if muxerName.len == 0 or muxerName == "na":
debug "no muxer available, early exit", conn
return
trace "Found a muxer", conn, muxerName
# create new muxer for connection
let muxer = s.muxers[muxerName].newMuxer(conn)
# install stream handler
muxer.streamHandler = s.streamHandler
s.connManager.storeOutgoing(conn)
# store it in muxed connections if we have a peer for it
s.connManager.storeMuxer(muxer, muxer.handle()) # store muxer and start read loop
try:
await s.identify(muxer)
except CatchableError as exc:
# Identify is non-essential, though if it fails, it might indicate that
# the connection was closed already - this will be picked up by the read
# loop
debug "Could not identify connection", conn, msg = exc.msg
return muxer
proc disconnect*(s: Switch, peerId: PeerID): Future[void] {.gcsafe.} = proc disconnect*(s: Switch, peerId: PeerID): Future[void] {.gcsafe.} =
s.connManager.dropPeer(peerId) s.connManager.dropPeer(peerId)
proc upgradeOutgoing(s: Switch, conn: Connection): Future[Connection] {.async, gcsafe.} =
trace "Upgrading outgoing connection", conn
let sconn = await s.secure(conn) # secure the connection
if isNil(sconn):
raise newException(UpgradeFailedError,
"unable to secure connection, stopping upgrade")
if sconn.peerInfo.isNil:
raise newException(UpgradeFailedError,
"current version of nim-libp2p requires that secure protocol negotiates peerid")
let muxer = await s.mux(sconn) # mux it if possible
if muxer == nil:
# TODO this might be relaxed in the future
raise newException(UpgradeFailedError,
"a muxer is required for outgoing connections")
if sconn.closed() or isNil(sconn.peerInfo):
await sconn.close()
raise newException(UpgradeFailedError,
"Connection closed or missing peer info, stopping upgrade")
trace "Upgraded outgoing connection", conn, sconn
return sconn
proc upgradeIncoming(s: Switch, incomingConn: Connection) {.async, gcsafe.} = # noraises
trace "Upgrading incoming connection", incomingConn
let ms = newMultistream()
# secure incoming connections
proc securedHandler(conn: Connection,
proto: string)
{.async, gcsafe, closure.} =
trace "Starting secure handler", conn
let secure = s.secureManagers.filterIt(it.codec == proto)[0]
var cconn = conn
try:
var sconn = await secure.secure(cconn, false)
if isNil(sconn):
return
cconn = sconn
# add the muxer
for muxer in s.muxers.values:
ms.addHandler(muxer.codecs, muxer)
# handle subsequent secure requests
await ms.handle(cconn)
except CatchableError as exc:
debug "Exception in secure handler during incoming upgrade", msg = exc.msg, conn
if not cconn.isUpgraded:
cconn.upgrade(exc)
finally:
if not isNil(cconn):
await cconn.close()
trace "Stopped secure handler", conn
try:
if (await ms.select(incomingConn)): # just handshake
# add the secure handlers
for k in s.secureManagers:
ms.addHandler(k.codec, securedHandler)
# handle un-secured connections
# we handshaked above, set this ms handler as active
await ms.handle(incomingConn, active = true)
except CatchableError as exc:
debug "Exception upgrading incoming", exc = exc.msg
if not incomingConn.isUpgraded:
incomingConn.upgrade(exc)
finally:
if not isNil(incomingConn):
await incomingConn.close()
proc dialAndUpgrade(s: Switch, proc dialAndUpgrade(s: Switch,
peerId: PeerID, peerId: PeerID,
addrs: seq[MultiAddress]): addrs: seq[MultiAddress]):
@ -295,7 +122,7 @@ proc dialAndUpgrade(s: Switch,
libp2p_successful_dials.inc() libp2p_successful_dials.inc()
let conn = try: let conn = try:
await s.upgradeOutgoing(dialed) await s.upgrade.upgradeOutgoing(dialed)
except CatchableError as exc: except CatchableError as exc:
# If we failed to establish the connection through one transport, # If we failed to establish the connection through one transport,
# we won't succeeded through another - no use in trying again # we won't succeeded through another - no use in trying again
@ -476,7 +303,7 @@ proc accept(s: Switch, transport: Transport) {.async.} = # noraises
debug "Accepted an incoming connection", conn debug "Accepted an incoming connection", conn
asyncSpawn upgradeMonitor(conn, upgrades) asyncSpawn upgradeMonitor(conn, upgrades)
asyncSpawn s.upgradeIncoming(conn) asyncSpawn s.upgrade.upgradeIncoming(conn)
except CancelledError as exc: except CancelledError as exc:
trace "releasing semaphore on cancellation" trace "releasing semaphore on cancellation"
upgrades.release() # always release the slot upgrades.release() # always release the slot
@ -529,39 +356,6 @@ proc stop*(s: Switch) {.async.} =
trace "Switch stopped" trace "Switch stopped"
proc muxerHandler(s: Switch, muxer: Muxer) {.async, gcsafe.} =
let
conn = muxer.connection
if conn.peerInfo.isNil:
warn "This version of nim-libp2p requires secure protocol to negotiate peerid"
await muxer.close()
return
# store incoming connection
s.connManager.storeIncoming(conn)
# store muxer and muxed connection
s.connManager.storeMuxer(muxer)
try:
await s.identify(muxer)
except IdentifyError as exc:
# Identify is non-essential, though if it fails, it might indicate that
# the connection was closed already - this will be picked up by the read
# loop
debug "Could not identify connection", conn, msg = exc.msg
except LPStreamClosedError as exc:
debug "Identify stream closed", conn, msg = exc.msg
except LPStreamEOFError as exc:
debug "Identify stream EOF", conn, msg = exc.msg
except CancelledError as exc:
await muxer.close()
raise exc
except CatchableError as exc:
await muxer.close()
trace "Exception in muxer handler", conn, msg = exc.msg
proc newSwitch*(peerInfo: PeerInfo, proc newSwitch*(peerInfo: PeerInfo,
transports: seq[Transport], transports: seq[Transport],
identity: Identify, identity: Identify,
@ -570,34 +364,19 @@ proc newSwitch*(peerInfo: PeerInfo,
if secureManagers.len == 0: if secureManagers.len == 0:
raise (ref CatchableError)(msg: "Provide at least one secure manager") raise (ref CatchableError)(msg: "Provide at least one secure manager")
let ms = newMultistream()
let connManager = ConnManager.init()
let upgrade = MuxedUpgrade.init(identity, muxers, secureManagers, connManager, ms)
let switch = Switch( let switch = Switch(
peerInfo: peerInfo, peerInfo: peerInfo,
ms: newMultistream(), ms: ms,
transports: transports, transports: transports,
connManager: ConnManager.init(), connManager: connManager,
identity: identity, upgrade: upgrade,
muxers: muxers,
secureManagers: @secureManagers,
) )
switch.streamHandler = proc(conn: Connection) {.async, gcsafe.} = # noraises
trace "Starting stream handler", conn
try:
await switch.ms.handle(conn) # handle incoming connection
except CancelledError as exc:
raise exc
except CatchableError as exc:
trace "exception in stream handler", conn, msg = exc.msg
finally:
await conn.closeWithEOF()
trace "Stream handler done", conn
switch.mount(identity) switch.mount(identity)
for key, val in muxers:
val.streamHandler = switch.streamHandler
val.muxerHandler = proc(muxer: Muxer): Future[void] =
switch.muxerHandler(muxer)
return switch return switch
proc isConnected*(s: Switch, peerInfo: PeerInfo): bool proc isConnected*(s: Switch, peerInfo: PeerInfo): bool

View File

@ -0,0 +1,213 @@
## Nim-LibP2P
## Copyright (c) 2021 Status Research & Development GmbH
## Licensed under either of
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
## at your option.
## This file may not be copied, modified, or distributed except according to
## those terms.
import std/[tables, sequtils]
import pkg/[chronos, chronicles, metrics]
import ../upgrademngrs/upgrade,
../muxers/muxer
export Upgrade
type
MuxedUpgrade* = ref object of Upgrade
muxers*: Table[string, MuxerProvider]
streamHandler*: StreamHandler
proc identify*(u: MuxedUpgrade, muxer: Muxer) {.async, gcsafe.} =
# new stream for identify
var stream = await muxer.newStream()
if stream == nil:
return
try:
await u.identify(stream)
finally:
await stream.closeWithEOF()
proc mux*(u: MuxedUpgrade, conn: Connection): Future[Muxer] {.async, gcsafe.} =
## mux incoming connection
trace "Muxing connection", conn
if u.muxers.len == 0:
warn "no muxers registered, skipping upgrade flow", conn
return
let muxerName = await u.ms.select(conn, toSeq(u.muxers.keys()))
if muxerName.len == 0 or muxerName == "na":
debug "no muxer available, early exit", conn
return
trace "Found a muxer", conn, muxerName
# create new muxer for connection
let muxer = u.muxers[muxerName].newMuxer(conn)
# install stream handler
muxer.streamHandler = u.streamHandler
u.connManager.storeOutgoing(conn)
# store it in muxed connections if we have a peer for it
u.connManager.storeMuxer(muxer, muxer.handle()) # store muxer and start read loop
try:
await u.identify(muxer)
except CatchableError as exc:
# Identify is non-essential, though if it fails, it might indicate that
# the connection was closed already - this will be picked up by the read
# loop
debug "Could not identify connection", conn, msg = exc.msg
return muxer
method upgradeOutgoing*(u: MuxedUpgrade, conn: Connection): Future[Connection] {.async, gcsafe.} =
trace "Upgrading outgoing connection", conn
let sconn = await u.secure(conn) # secure the connection
if isNil(sconn):
raise newException(UpgradeFailedError,
"unable to secure connection, stopping upgrade")
if sconn.peerInfo.isNil:
raise newException(UpgradeFailedError,
"current version of nim-libp2p requires that secure protocol negotiates peerid")
let muxer = await u.mux(sconn) # mux it if possible
if muxer == nil:
# TODO this might be relaxed in the future
raise newException(UpgradeFailedError,
"a muxer is required for outgoing connections")
if sconn.closed() or isNil(sconn.peerInfo):
await sconn.close()
raise newException(UpgradeFailedError,
"Connection closed or missing peer info, stopping upgrade")
trace "Upgraded outgoing connection", conn, sconn
return sconn
method upgradeIncoming*(u: MuxedUpgrade, incomingConn: Connection) {.async, gcsafe.} = # noraises
trace "Upgrading incoming connection", incomingConn
let ms = newMultistream()
# secure incoming connections
proc securedHandler(conn: Connection,
proto: string)
{.async, gcsafe, closure.} =
trace "Starting secure handler", conn
let secure = u.secureManagers.filterIt(it.codec == proto)[0]
var cconn = conn
try:
var sconn = await secure.secure(cconn, false)
if isNil(sconn):
return
cconn = sconn
# add the muxer
for muxer in u.muxers.values:
ms.addHandler(muxer.codecs, muxer)
# handle subsequent secure requests
await ms.handle(cconn)
except CatchableError as exc:
debug "Exception in secure handler during incoming upgrade", msg = exc.msg, conn
if not cconn.isUpgraded:
cconn.upgrade(exc)
finally:
if not isNil(cconn):
await cconn.close()
trace "Stopped secure handler", conn
try:
if (await ms.select(incomingConn)): # just handshake
# add the secure handlers
for k in u.secureManagers:
ms.addHandler(k.codec, securedHandler)
# handle un-secured connections
# we handshaked above, set this ms handler as active
await ms.handle(incomingConn, active = true)
except CatchableError as exc:
debug "Exception upgrading incoming", exc = exc.msg
if not incomingConn.isUpgraded:
incomingConn.upgrade(exc)
finally:
if not isNil(incomingConn):
await incomingConn.close()
proc muxerHandler(u: MuxedUpgrade, muxer: Muxer) {.async, gcsafe.} =
let
conn = muxer.connection
if conn.peerInfo.isNil:
warn "This version of nim-libp2p requires secure protocol to negotiate peerid"
await muxer.close()
return
# store incoming connection
u.connManager.storeIncoming(conn)
# store muxer and muxed connection
u.connManager.storeMuxer(muxer)
try:
await u.identify(muxer)
except IdentifyError as exc:
# Identify is non-essential, though if it fails, it might indicate that
# the connection was closed already - this will be picked up by the read
# loop
debug "Could not identify connection", conn, msg = exc.msg
except LPStreamClosedError as exc:
debug "Identify stream closed", conn, msg = exc.msg
except LPStreamEOFError as exc:
debug "Identify stream EOF", conn, msg = exc.msg
except CancelledError as exc:
await muxer.close()
raise exc
except CatchableError as exc:
await muxer.close()
trace "Exception in muxer handler", conn, msg = exc.msg
proc init*(
T: type MuxedUpgrade,
identity: Identify,
muxers: Table[string, MuxerProvider],
secureManagers: openarray[Secure] = [],
connManager: ConnManager,
ms: MultistreamSelect): T =
let upgrader = T(
identity: identity,
muxers: muxers,
secureManagers: @secureManagers,
connManager: connManager,
ms: ms)
upgrader.streamHandler = proc(conn: Connection) {.async, gcsafe.} = # noraises
trace "Starting stream handler", conn
try:
await upgrader.ms.handle(conn) # handle incoming connection
except CancelledError as exc:
raise exc
except CatchableError as exc:
trace "exception in stream handler", conn, msg = exc.msg
finally:
await conn.closeWithEOF()
trace "Stream handler done", conn
for _, val in muxers:
val.streamHandler = upgrader.streamHandler
val.muxerHandler = proc(muxer: Muxer): Future[void] =
upgrader.muxerHandler(muxer)
return upgrader

View File

@ -0,0 +1,80 @@
## Nim-LibP2P
## Copyright (c) 2021 Status Research & Development GmbH
## Licensed under either of
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
## at your option.
## This file may not be copied, modified, or distributed except according to
## those terms.
import std/[options, sequtils]
import pkg/[chronos, chronicles, metrics]
import ../stream/connection,
../protocols/secure/secure,
../protocols/identify,
../multistream,
../connmanager
export connmanager, connection, identify, secure, multistream
declarePublicCounter(libp2p_failed_upgrade, "peers failed upgrade")
type
UpgradeFailedError* = object of CatchableError
Upgrade* = ref object of RootObj
ms*: MultistreamSelect
identity*: Identify
connManager*: ConnManager
secureManagers*: seq[Secure]
method upgradeIncoming*(u: Upgrade, conn: Connection): Future[void] {.base.} =
doAssert(false, "Not implemented!")
method upgradeOutgoing*(u: Upgrade, conn: Connection): Future[Connection] {.base.} =
doAssert(false, "Not implemented!")
proc secure*(u: Upgrade, conn: Connection): Future[Connection] {.async, gcsafe.} =
if u.secureManagers.len <= 0:
raise newException(UpgradeFailedError, "No secure managers registered!")
let codec = await u.ms.select(conn, u.secureManagers.mapIt(it.codec))
if codec.len == 0:
raise newException(UpgradeFailedError, "Unable to negotiate a secure channel!")
trace "Securing connection", conn, codec
let secureProtocol = u.secureManagers.filterIt(it.codec == codec)
# ms.select should deal with the correctness of this
# let's avoid duplicating checks but detect if it fails to do it properly
doAssert(secureProtocol.len > 0)
return await secureProtocol[0].secure(conn, true)
proc identify*(u: Upgrade, conn: Connection) {.async, gcsafe.} =
## identify the connection
if (await u.ms.select(conn, u.identity.codec)):
let info = await u.identity.identify(conn, conn.peerInfo)
if info.pubKey.isNone and isNil(conn):
raise newException(UpgradeFailedError,
"no public key provided and no existing peer identity found")
if isNil(conn.peerInfo):
conn.peerInfo = PeerInfo.init(info.pubKey.get())
if info.addrs.len > 0:
conn.peerInfo.addrs = info.addrs
if info.agentVersion.isSome:
conn.peerInfo.agentVersion = info.agentVersion.get()
if info.protoVersion.isSome:
conn.peerInfo.protoVersion = info.protoVersion.get()
if info.protos.len > 0:
conn.peerInfo.protocols = info.protos
trace "identified remote peer", conn, peerInfo = shortLog(conn.peerInfo)