2019-08-27 21:46:12 +00:00
|
|
|
## Nim-LibP2P
|
2019-09-24 17:48:23 +00:00
|
|
|
## Copyright (c) 2019 Status Research & Development GmbH
|
2019-08-27 21:46:12 +00:00
|
|
|
## 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.
|
|
|
|
|
2021-05-21 16:27:01 +00:00
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
2021-01-21 04:00:24 +00:00
|
|
|
import std/[tables,
|
|
|
|
options,
|
|
|
|
sets,
|
|
|
|
oids,
|
|
|
|
sugar,
|
|
|
|
math]
|
2020-06-24 15:08:44 +00:00
|
|
|
|
|
|
|
import chronos,
|
|
|
|
chronicles,
|
|
|
|
metrics
|
|
|
|
|
2020-06-19 17:29:43 +00:00
|
|
|
import stream/connection,
|
2019-09-11 19:03:30 +00:00
|
|
|
transports/transport,
|
2021-01-20 17:28:32 +00:00
|
|
|
upgrademngrs/[upgrade, muxedupgrade],
|
2019-09-11 19:03:30 +00:00
|
|
|
multistream,
|
2020-06-17 04:14:02 +00:00
|
|
|
multiaddress,
|
2019-09-06 07:13:47 +00:00
|
|
|
protocols/protocol,
|
2019-09-14 13:55:52 +00:00
|
|
|
protocols/secure/secure,
|
2019-09-11 19:03:30 +00:00
|
|
|
peerinfo,
|
|
|
|
protocols/identify,
|
2019-09-06 07:13:47 +00:00
|
|
|
muxers/muxer,
|
2021-01-04 18:59:05 +00:00
|
|
|
utils/semaphore,
|
2020-07-17 15:36:48 +00:00
|
|
|
connmanager,
|
2021-08-18 07:40:12 +00:00
|
|
|
nameresolving/nameresolver,
|
2020-07-24 19:24:31 +00:00
|
|
|
peerid,
|
2021-06-08 16:55:24 +00:00
|
|
|
peerstore,
|
2021-03-18 15:20:36 +00:00
|
|
|
errors,
|
|
|
|
dialer
|
2019-08-28 19:12:15 +00:00
|
|
|
|
2021-06-08 16:55:24 +00:00
|
|
|
export connmanager, upgrade, dialer, peerstore
|
2020-09-23 14:07:16 +00:00
|
|
|
|
2019-09-12 02:10:38 +00:00
|
|
|
logScope:
|
2020-12-01 17:34:27 +00:00
|
|
|
topics = "libp2p switch"
|
2019-09-12 02:10:38 +00:00
|
|
|
|
2019-12-04 04:44:54 +00:00
|
|
|
#TODO: General note - use a finite state machine to manage the different
|
2019-12-08 21:06:58 +00:00
|
|
|
# steps of connections establishing and upgrading. This makes everything
|
|
|
|
# more robust and less prone to ordering attacks - i.e. muxing can come if
|
|
|
|
# and only if the channel has been secured (i.e. if a secure manager has been
|
2019-12-04 04:44:54 +00:00
|
|
|
# previously provided)
|
|
|
|
|
2021-01-07 23:06:41 +00:00
|
|
|
declareCounter(libp2p_failed_upgrades_incoming, "incoming connections failed upgrades")
|
2020-06-11 14:45:59 +00:00
|
|
|
|
2021-01-04 18:59:05 +00:00
|
|
|
const
|
|
|
|
ConcurrentUpgrades* = 4
|
|
|
|
|
2019-08-28 19:12:15 +00:00
|
|
|
type
|
2021-03-18 15:20:36 +00:00
|
|
|
Switch* = ref object of Dial
|
2019-08-30 05:17:07 +00:00
|
|
|
peerInfo*: PeerInfo
|
2021-01-15 04:48:03 +00:00
|
|
|
connManager*: ConnManager
|
2019-08-30 05:17:07 +00:00
|
|
|
transports*: seq[Transport]
|
2020-02-04 16:16:21 +00:00
|
|
|
ms*: MultistreamSelect
|
2020-11-19 02:06:42 +00:00
|
|
|
acceptFuts: seq[Future[void]]
|
2021-03-18 15:20:36 +00:00
|
|
|
dialer*: Dial
|
2021-06-08 16:55:24 +00:00
|
|
|
peerStore*: PeerStore
|
2021-08-18 07:40:12 +00:00
|
|
|
nameResolver*: NameResolver
|
2019-08-27 21:46:12 +00:00
|
|
|
|
2020-08-08 06:52:20 +00:00
|
|
|
proc addConnEventHandler*(s: Switch,
|
2020-09-23 14:07:16 +00:00
|
|
|
handler: ConnEventHandler,
|
|
|
|
kind: ConnEventKind) =
|
|
|
|
s.connManager.addConnEventHandler(handler, kind)
|
2020-07-24 19:24:31 +00:00
|
|
|
|
2020-08-08 06:52:20 +00:00
|
|
|
proc removeConnEventHandler*(s: Switch,
|
2020-09-23 14:07:16 +00:00
|
|
|
handler: ConnEventHandler,
|
|
|
|
kind: ConnEventKind) =
|
|
|
|
s.connManager.removeConnEventHandler(handler, kind)
|
2020-06-24 15:08:44 +00:00
|
|
|
|
2020-09-15 20:19:22 +00:00
|
|
|
proc addPeerEventHandler*(s: Switch,
|
|
|
|
handler: PeerEventHandler,
|
2020-11-28 16:59:47 +00:00
|
|
|
kind: PeerEventKind) =
|
2020-09-23 14:07:16 +00:00
|
|
|
s.connManager.addPeerEventHandler(handler, kind)
|
2020-09-15 20:19:22 +00:00
|
|
|
|
|
|
|
proc removePeerEventHandler*(s: Switch,
|
|
|
|
handler: PeerEventHandler,
|
2020-11-28 16:59:47 +00:00
|
|
|
kind: PeerEventKind) =
|
2020-09-23 14:07:16 +00:00
|
|
|
s.connManager.removePeerEventHandler(handler, kind)
|
2020-09-15 20:19:22 +00:00
|
|
|
|
2020-08-06 07:29:27 +00:00
|
|
|
proc isConnected*(s: Switch, peerId: PeerID): bool =
|
2020-07-17 18:44:41 +00:00
|
|
|
## returns true if the peer has one or more
|
|
|
|
## associated connections (sockets)
|
|
|
|
##
|
|
|
|
|
2020-08-06 07:29:27 +00:00
|
|
|
peerId in s.connManager
|
2020-07-08 00:33:05 +00:00
|
|
|
|
2020-08-06 07:29:27 +00:00
|
|
|
proc disconnect*(s: Switch, peerId: PeerID): Future[void] {.gcsafe.} =
|
|
|
|
s.connManager.dropPeer(peerId)
|
2019-09-28 19:54:32 +00:00
|
|
|
|
2021-03-18 15:20:36 +00:00
|
|
|
method connect*(
|
|
|
|
s: Switch,
|
|
|
|
peerId: PeerID,
|
|
|
|
addrs: seq[MultiAddress]): Future[void] =
|
|
|
|
s.dialer.connect(peerId, addrs)
|
2020-02-25 22:13:05 +00:00
|
|
|
|
2021-03-18 15:20:36 +00:00
|
|
|
method dial*(
|
|
|
|
s: Switch,
|
|
|
|
peerId: PeerID,
|
|
|
|
protos: seq[string]): Future[Connection] =
|
|
|
|
s.dialer.dial(peerId, protos)
|
2020-09-21 09:16:29 +00:00
|
|
|
|
|
|
|
proc dial*(s: Switch,
|
|
|
|
peerId: PeerID,
|
2021-01-21 04:00:24 +00:00
|
|
|
proto: string): Future[Connection] =
|
|
|
|
dial(s, peerId, @[proto])
|
2020-08-12 00:05:49 +00:00
|
|
|
|
2021-03-18 15:20:36 +00:00
|
|
|
method dial*(
|
|
|
|
s: Switch,
|
|
|
|
peerId: PeerID,
|
|
|
|
addrs: seq[MultiAddress],
|
|
|
|
protos: seq[string]): Future[Connection] =
|
|
|
|
s.dialer.dial(peerId, addrs, protos)
|
2020-07-08 00:33:05 +00:00
|
|
|
|
2021-03-18 15:20:36 +00:00
|
|
|
proc dial*(
|
|
|
|
s: Switch,
|
|
|
|
peerId: PeerID,
|
|
|
|
addrs: seq[MultiAddress],
|
|
|
|
proto: string): Future[Connection] =
|
|
|
|
dial(s, peerId, addrs, @[proto])
|
2020-09-21 09:16:29 +00:00
|
|
|
|
2021-05-21 16:27:01 +00:00
|
|
|
proc mount*[T: LPProtocol](s: Switch, proto: T, matcher: Matcher = nil)
|
2021-06-02 13:39:10 +00:00
|
|
|
{.gcsafe, raises: [Defect, LPError].} =
|
2019-08-31 17:58:49 +00:00
|
|
|
if isNil(proto.handler):
|
2021-06-02 13:39:10 +00:00
|
|
|
raise newException(LPError,
|
2020-06-27 17:33:34 +00:00
|
|
|
"Protocol has to define a handle method or proc")
|
2019-08-27 21:46:12 +00:00
|
|
|
|
2019-09-06 07:13:47 +00:00
|
|
|
if proto.codec.len == 0:
|
2021-06-02 13:39:10 +00:00
|
|
|
raise newException(LPError,
|
2020-06-27 17:33:34 +00:00
|
|
|
"Protocol has to define a codec string")
|
2019-08-31 18:52:56 +00:00
|
|
|
|
2020-10-02 14:59:15 +00:00
|
|
|
s.ms.addHandler(proto.codecs, proto, matcher)
|
2021-03-15 21:29:05 +00:00
|
|
|
s.peerInfo.protocols.add(proto.codec)
|
2019-08-28 02:30:53 +00:00
|
|
|
|
2021-01-04 18:59:05 +00:00
|
|
|
proc upgradeMonitor(conn: Connection, upgrades: AsyncSemaphore) {.async.} =
|
|
|
|
## monitor connection for upgrades
|
|
|
|
##
|
|
|
|
try:
|
|
|
|
# Since we don't control the flow of the
|
|
|
|
# upgrade, this timeout guarantees that a
|
|
|
|
# "hanged" remote doesn't hold the upgrade
|
|
|
|
# forever
|
2021-01-18 22:27:29 +00:00
|
|
|
await conn.onUpgrade.wait(30.seconds) # wait for connection to be upgraded
|
2021-01-04 18:59:05 +00:00
|
|
|
trace "Connection upgrade succeeded"
|
|
|
|
except CatchableError as exc:
|
2021-01-07 23:06:41 +00:00
|
|
|
libp2p_failed_upgrades_incoming.inc()
|
|
|
|
if not isNil(conn):
|
|
|
|
await conn.close()
|
2021-01-04 18:59:05 +00:00
|
|
|
|
|
|
|
trace "Exception awaiting connection upgrade", exc = exc.msg, conn
|
|
|
|
finally:
|
|
|
|
upgrades.release() # don't forget to release the slot!
|
|
|
|
|
2020-11-19 02:06:42 +00:00
|
|
|
proc accept(s: Switch, transport: Transport) {.async.} = # noraises
|
2021-01-04 18:59:05 +00:00
|
|
|
## switch accept loop, ran for every transport
|
2020-11-19 02:06:42 +00:00
|
|
|
##
|
2019-12-04 04:44:54 +00:00
|
|
|
|
2021-01-14 09:11:12 +00:00
|
|
|
let upgrades = newAsyncSemaphore(ConcurrentUpgrades)
|
2020-11-19 02:06:42 +00:00
|
|
|
while transport.running:
|
|
|
|
var conn: Connection
|
2019-09-04 22:00:39 +00:00
|
|
|
try:
|
2020-11-19 02:06:42 +00:00
|
|
|
debug "About to accept incoming connection"
|
2021-01-04 18:59:05 +00:00
|
|
|
# remember to always release the slot when
|
|
|
|
# the upgrade succeeds or fails, this is
|
|
|
|
# currently done by the `upgradeMonitor`
|
2021-01-21 04:00:24 +00:00
|
|
|
await upgrades.acquire() # first wait for an upgrade slot to become available
|
|
|
|
conn = await s.connManager # next attempt to get an incoming connection
|
|
|
|
.trackIncomingConn(
|
|
|
|
() => transport.accept()
|
|
|
|
)
|
2021-01-04 18:59:05 +00:00
|
|
|
if isNil(conn):
|
2020-11-19 02:06:42 +00:00
|
|
|
# A nil connection means that we might have hit a
|
|
|
|
# file-handle limit (or another non-fatal error),
|
|
|
|
# we can get one on the next try, but we should
|
|
|
|
# be careful to not end up in a thigh loop that
|
|
|
|
# will starve the main event loop, thus we sleep
|
|
|
|
# here before retrying.
|
2021-01-04 18:59:05 +00:00
|
|
|
trace "Unable to get a connection, sleeping"
|
2020-11-19 02:06:42 +00:00
|
|
|
await sleepAsync(100.millis) # TODO: should be configurable?
|
2021-01-04 18:59:05 +00:00
|
|
|
upgrades.release()
|
|
|
|
continue
|
|
|
|
|
2021-03-02 23:23:40 +00:00
|
|
|
# set the direction of this bottom level transport
|
|
|
|
# in order to be able to consume this information in gossipsub if required
|
|
|
|
# gossipsub gives priority to connections we make
|
|
|
|
conn.transportDir = Direction.In
|
|
|
|
|
2021-01-04 18:59:05 +00:00
|
|
|
debug "Accepted an incoming connection", conn
|
|
|
|
asyncSpawn upgradeMonitor(conn, upgrades)
|
2021-03-18 15:20:36 +00:00
|
|
|
asyncSpawn transport.upgradeIncoming(conn)
|
2021-01-04 18:59:05 +00:00
|
|
|
except CancelledError as exc:
|
|
|
|
trace "releasing semaphore on cancellation"
|
|
|
|
upgrades.release() # always release the slot
|
2019-12-04 04:44:54 +00:00
|
|
|
except CatchableError as exc:
|
2020-11-19 02:06:42 +00:00
|
|
|
debug "Exception in accept loop, exiting", exc = exc.msg
|
2021-01-04 18:59:05 +00:00
|
|
|
upgrades.release() # always release the slot
|
2020-11-19 02:06:42 +00:00
|
|
|
if not isNil(conn):
|
|
|
|
await conn.close()
|
|
|
|
return
|
|
|
|
|
|
|
|
proc start*(s: Switch): Future[seq[Future[void]]] {.async, gcsafe.} =
|
|
|
|
trace "starting switch for peer", peerInfo = s.peerInfo
|
2019-09-12 00:15:04 +00:00
|
|
|
var startFuts: seq[Future[void]]
|
2019-08-28 02:30:53 +00:00
|
|
|
for t in s.transports: # for each transport
|
2019-09-28 19:54:32 +00:00
|
|
|
for i, a in s.peerInfo.addrs:
|
2019-08-28 02:30:53 +00:00
|
|
|
if t.handles(a): # check if it handles the multiaddr
|
2020-11-19 02:06:42 +00:00
|
|
|
var server = t.start(a)
|
2019-09-28 19:54:32 +00:00
|
|
|
s.peerInfo.addrs[i] = t.ma # update peer's address
|
2020-11-19 02:06:42 +00:00
|
|
|
s.acceptFuts.add(s.accept(t))
|
2019-09-12 00:15:04 +00:00
|
|
|
startFuts.add(server)
|
2019-12-08 21:06:58 +00:00
|
|
|
|
2021-06-08 16:55:24 +00:00
|
|
|
proc peerIdentifiedHandler(peerInfo: PeerInfo, event: PeerEvent) {.async.} =
|
|
|
|
s.peerStore.replace(peerInfo)
|
|
|
|
|
|
|
|
s.connManager.addPeerEventHandler(peerIdentifiedHandler, PeerEventKind.Identified)
|
|
|
|
|
2020-09-07 12:15:11 +00:00
|
|
|
debug "Started libp2p node", peer = s.peerInfo
|
2020-11-19 02:06:42 +00:00
|
|
|
return startFuts # listen for incoming connections
|
2019-08-27 21:46:12 +00:00
|
|
|
|
2019-12-08 21:06:58 +00:00
|
|
|
proc stop*(s: Switch) {.async.} =
|
2020-09-09 17:12:08 +00:00
|
|
|
trace "Stopping switch"
|
2020-06-29 15:15:31 +00:00
|
|
|
|
2020-07-17 15:36:48 +00:00
|
|
|
# close and cleanup all connections
|
|
|
|
await s.connManager.close()
|
2019-09-06 07:13:47 +00:00
|
|
|
|
2020-06-29 15:15:31 +00:00
|
|
|
for t in s.transports:
|
|
|
|
try:
|
2020-11-19 02:06:42 +00:00
|
|
|
await t.stop()
|
2020-06-29 15:15:31 +00:00
|
|
|
except CancelledError as exc:
|
|
|
|
raise exc
|
|
|
|
except CatchableError as exc:
|
2020-09-09 17:12:08 +00:00
|
|
|
warn "error cleaning up transports", msg = exc.msg
|
2020-06-29 15:15:31 +00:00
|
|
|
|
2021-01-04 18:59:05 +00:00
|
|
|
try:
|
|
|
|
await allFutures(s.acceptFuts)
|
|
|
|
.wait(1.seconds)
|
|
|
|
except CatchableError as exc:
|
|
|
|
trace "Exception while stopping accept loops", exc = exc.msg
|
|
|
|
|
|
|
|
# check that all futures were properly
|
|
|
|
# stopped and otherwise cancel them
|
|
|
|
for a in s.acceptFuts:
|
|
|
|
if not a.finished:
|
|
|
|
a.cancel()
|
|
|
|
|
2020-09-09 17:12:08 +00:00
|
|
|
trace "Switch stopped"
|
2020-04-21 01:24:42 +00:00
|
|
|
|
2019-09-06 07:13:47 +00:00
|
|
|
proc newSwitch*(peerInfo: PeerInfo,
|
|
|
|
transports: seq[Transport],
|
|
|
|
identity: Identify,
|
2019-09-09 17:33:32 +00:00
|
|
|
muxers: Table[string, MuxerProvider],
|
2021-01-21 04:00:24 +00:00
|
|
|
secureManagers: openarray[Secure] = [],
|
2021-03-18 15:20:36 +00:00
|
|
|
connManager: ConnManager,
|
2021-08-18 07:40:12 +00:00
|
|
|
ms: MultistreamSelect,
|
|
|
|
nameResolver: NameResolver = nil): Switch
|
2021-05-24 17:55:33 +00:00
|
|
|
{.raises: [Defect, LPError].} =
|
2020-07-07 11:14:11 +00:00
|
|
|
if secureManagers.len == 0:
|
2021-05-24 17:55:33 +00:00
|
|
|
raise newException(LPError, "Provide at least one secure manager")
|
2020-07-07 11:14:11 +00:00
|
|
|
|
2020-09-15 20:19:22 +00:00
|
|
|
let switch = Switch(
|
2020-07-07 11:14:11 +00:00
|
|
|
peerInfo: peerInfo,
|
2021-01-20 17:28:32 +00:00
|
|
|
ms: ms,
|
2020-07-07 11:14:11 +00:00
|
|
|
transports: transports,
|
2021-01-20 17:28:32 +00:00
|
|
|
connManager: connManager,
|
2021-06-08 16:55:24 +00:00
|
|
|
peerStore: PeerStore.new(),
|
2021-08-18 07:40:12 +00:00
|
|
|
dialer: Dialer.new(peerInfo, connManager, transports, ms),
|
|
|
|
nameResolver: nameResolver)
|
2019-09-06 07:13:47 +00:00
|
|
|
|
2020-09-15 20:19:22 +00:00
|
|
|
switch.mount(identity)
|
|
|
|
return switch
|
2020-05-21 17:33:48 +00:00
|
|
|
|
2020-08-12 00:05:49 +00:00
|
|
|
proc isConnected*(s: Switch, peerInfo: PeerInfo): bool
|
|
|
|
{.deprecated: "Use PeerID version".} =
|
2020-08-06 07:29:27 +00:00
|
|
|
not isNil(peerInfo) and isConnected(s, peerInfo.peerId)
|
|
|
|
|
2020-08-12 00:05:49 +00:00
|
|
|
proc disconnect*(s: Switch, peerInfo: PeerInfo): Future[void]
|
|
|
|
{.deprecated: "Use PeerID version", gcsafe.} =
|
2020-08-06 07:29:27 +00:00
|
|
|
disconnect(s, peerInfo.peerId)
|
|
|
|
|
2020-08-12 00:05:49 +00:00
|
|
|
proc connect*(s: Switch, peerInfo: PeerInfo): Future[void]
|
|
|
|
{.deprecated: "Use PeerID version".} =
|
2020-08-06 07:29:27 +00:00
|
|
|
connect(s, peerInfo.peerId, peerInfo.addrs)
|
|
|
|
|
|
|
|
proc dial*(s: Switch,
|
|
|
|
peerInfo: PeerInfo,
|
|
|
|
proto: string):
|
2020-08-12 00:05:49 +00:00
|
|
|
Future[Connection]
|
|
|
|
{.deprecated: "Use PeerID version".} =
|
2020-08-06 07:29:27 +00:00
|
|
|
dial(s, peerInfo.peerId, peerInfo.addrs, proto)
|