Compare commits

...

20 Commits

Author SHA1 Message Date
Diego
fdff1ebec5 use di to inject network interface provider 2024-05-26 22:24:14 +02:00
Diego
d34575675c create di 2024-05-26 22:23:25 +02:00
diegomrsantos
03f72a8b5c enable resolver by default 2024-05-26 18:29:34 +02:00
diegomrsantos
b11e2b0349 Update libp2p/services/wildcardresolverservice.nim
Co-authored-by: Ludovic Chenut <ludovic@status.im>
2024-05-24 14:13:35 +02:00
Diego
e228981a11 remove echo 2024-05-23 17:10:37 +02:00
Diego
907c41a491 remove toOpt 2024-05-23 17:09:56 +02:00
Diego
03668a3e90 formatting 2024-05-23 17:07:18 +02:00
Diego
980950b147 make addr provider a proc 2024-05-23 17:06:25 +02:00
Diego
eb6a9f2ff1 add docs 2024-05-22 15:28:38 +02:00
Diego
7810dba9d6 remove redundant test 2024-05-17 14:58:26 +02:00
Diego
6d327c37c4 remove peer id concat 2024-05-17 14:51:04 +02:00
Diego
21cb13a8ff remove unnecessary check 2024-05-17 01:21:11 +02:00
Diego
1b2b009f79 move peer id concatenation to switch 2024-05-17 01:19:23 +02:00
Diego
3a659ffddb removes scheduler and some cleanup 2024-05-16 19:20:30 +02:00
Diego
ac994a8f15 add doc comments 2024-05-16 18:39:58 +02:00
Diego
ee8318ec42 add doc comments 2024-05-16 16:08:33 +02:00
diegomrsantos
52a8870f78 Merge branch 'master' into anyaddr-resolver 2024-05-15 20:53:59 +02:00
Diego
d7c0486968 add withvalue for result 2024-05-15 16:14:37 +02:00
Diego
63b6390d1a add test 2024-05-15 16:14:23 +02:00
Diego
cf7b77bf82 add wildcard addr resolver 2024-05-15 16:14:18 +02:00
8 changed files with 409 additions and 14 deletions

24
di/di.nim Normal file
View File

@@ -0,0 +1,24 @@
import typetraits
import tables
type
BindingKey = tuple[typeName: string, qualifier: string]
Container* = ref object
bindings*: Table[BindingKey, proc(): RootRef {.gcsafe, raises: [].}]
BindingNotFoundError* = object of CatchableError
proc register*[T](c: Container, implementation: proc(): T {.gcsafe, raises: [].}, qualifier: string = "") =
let key: BindingKey = (name(T), qualifier)
proc p(): RootRef =
let o: RootRef = implementation()
return o
c.bindings[key] = p
proc resolve*[T](c: Container, qualifier: string = ""): T {.raises: [BindingNotFoundError]} =
let key: BindingKey = (name(T), qualifier)
try:
return cast[T](c.bindings[key]())
except KeyError:
raise newException(BindingNotFoundError, "Type not bound: " & name(T))

View File

@@ -19,7 +19,8 @@ runnableExamples:
{.push raises: [].}
import
options, tables, chronos, chronicles, sequtils,
options, tables, chronos, chronicles, sequtils
import
switch, peerid, peerinfo, stream/connection, multiaddress,
crypto/crypto, transports/[transport, tcptransport],
muxers/[muxer, mplex/mplex, yamux/yamux],
@@ -28,6 +29,8 @@ import
connmanager, upgrademngrs/muxedupgrade, observedaddrmanager,
nameresolving/nameresolver,
errors, utility
import services/wildcardresolverservice
import ../di/di
export
switch, peerid, peerinfo, connection, multiaddress, crypto, errors
@@ -59,6 +62,8 @@ type
rdv: RendezVous
services: seq[Service]
observedAddrManager: ObservedAddrManager
enableWildcardResolver: bool
container*: Container
proc new*(T: type[SwitchBuilder]): T {.public.} =
## Creates a SwitchBuilder
@@ -67,7 +72,7 @@ proc new*(T: type[SwitchBuilder]): T {.public.} =
.init("/ip4/127.0.0.1/tcp/0")
.expect("Should initialize to default")
SwitchBuilder(
let sb = SwitchBuilder(
privKey: none(PrivateKey),
addresses: @[address],
secureManagers: @[],
@@ -76,7 +81,12 @@ proc new*(T: type[SwitchBuilder]): T {.public.} =
maxOut: -1,
maxConnsPerPeer: MaxConnectionsPerPeer,
protoVersion: ProtoVersion,
agentVersion: AgentVersion)
agentVersion: AgentVersion,
container: Container())
register[NetworkInterfaceProvider](sb.container, networkInterfaceProvider)
sb
proc withPrivateKey*(b: SwitchBuilder, privateKey: PrivateKey): SwitchBuilder {.public.} =
## Set the private key of the switch. Will be used to
@@ -85,20 +95,19 @@ proc withPrivateKey*(b: SwitchBuilder, privateKey: PrivateKey): SwitchBuilder {.
b.privKey = some(privateKey)
b
proc withAddress*(b: SwitchBuilder, address: MultiAddress): SwitchBuilder {.public.} =
## | Set the listening address of the switch
## | Calling it multiple time will override the value
b.addresses = @[address]
b
proc withAddresses*(b: SwitchBuilder, addresses: seq[MultiAddress]): SwitchBuilder {.public.} =
proc withAddresses*(b: SwitchBuilder, addresses: seq[MultiAddress], enableWildcardResolver: bool = true): SwitchBuilder {.public.} =
## | Set the listening addresses of the switch
## | Calling it multiple time will override the value
b.addresses = addresses
b.enableWildcardResolver = enableWildcardResolver
b
proc withAddress*(b: SwitchBuilder, address: MultiAddress, enableWildcardResolver: bool = true): SwitchBuilder {.public.} =
## | Set the listening address of the switch
## | Calling it multiple time will override the value
b.withAddresses(@[address], enableWildcardResolver)
proc withSignedPeerRecord*(b: SwitchBuilder, sendIt = true): SwitchBuilder {.public.} =
b.sendSignedPeerRecord = sendIt
b
@@ -209,6 +218,10 @@ proc withObservedAddrManager*(b: SwitchBuilder, observedAddrManager: ObservedAdd
b.observedAddrManager = observedAddrManager
b
proc withBinding*[T](b: SwitchBuilder, binding: proc(): T {.gcsafe, raises: [].}): SwitchBuilder =
register[T](b.container, binding)
b
proc build*(b: SwitchBuilder): Switch
{.raises: [LPError], public.} =
@@ -261,6 +274,12 @@ proc build*(b: SwitchBuilder): Switch
else:
PeerStore.new(identify)
try:
let networkInterfaceProvider = resolve[NetworkInterfaceProvider](b.container)
b.services.add(WildcardAddressResolverService.new(networkInterfaceProvider))
except BindingNotFoundError as e:
raise newException(LPError, "Cannot resolve NetworkInterfaceProvider", e)
let switch = newSwitch(
peerInfo = peerInfo,
transports = transports,
@@ -312,7 +331,7 @@ proc newStandardSwitch*(
let addrs = when addrs is MultiAddress: @[addrs] else: addrs
var b = SwitchBuilder
.new()
.withAddresses(addrs)
.withAddresses(addrs, true)
.withRng(rng)
.withSignedPeerRecord(sendSignedPeerRecord)
.withMaxConnections(maxConnections)

View File

@@ -18,7 +18,7 @@ import tables, strutils, sets
import multicodec, multihash, multibase, transcoder, vbuffer, peerid,
protobuf/minprotobuf, errors, utility
import stew/[base58, base32, endians2, results]
export results, minprotobuf, vbuffer, utility
export results, minprotobuf, vbuffer, utility, multicodec
logScope:
topics = "libp2p multiaddress"

View File

@@ -24,12 +24,16 @@ type
AddressMapper* =
proc(listenAddrs: seq[MultiAddress]): Future[seq[MultiAddress]]
{.gcsafe, raises: [].}
## A proc that expected to resolve the listen addresses into dialable addresses
PeerInfo* {.public.} = ref object
peerId*: PeerId
listenAddrs*: seq[MultiAddress]
## contains addresses the node listens on, which may include wildcard and private addresses (not directly reachable).
addrs: seq[MultiAddress]
## contains resolved addresses that other peers can use to connect, including public-facing NAT and port-forwarded addresses.
addressMappers*: seq[AddressMapper]
## contains a list of procs that can be used to resolve the listen addresses into dialable addresses.
protocols*: seq[string]
protoVersion*: string
agentVersion*: string

View File

@@ -0,0 +1,213 @@
# Nim-LibP2P
# Copyright (c) 2024 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.
{.push raises: [].}
import std/sequtils
import stew/[byteutils, results, endians2]
import chronos, chronos/transports/[osnet, ipnet], chronicles
import ../[multiaddress, multicodec]
import ../switch
logScope:
topics = "libp2p wildcardresolverservice"
type
WildcardAddressResolverService* = ref object of Service
## Service used to resolve wildcard addresses of the type "0.0.0.0" for IPv4 or "::" for IPv6.
## When used with a `Switch`, this service will be automatically set up and stopped
## when the switch starts and stops. This is facilitated by adding the service to the switch's
## list of services using the `.withServices(@[svc])` method in the `SwitchBuilder`.
networkInterfaceProvider: NetworkInterfaceProvider
## Provides a list of network addresses.
addressMapper: AddressMapper
## An implementation of an address mapper that takes a list of listen addresses and expands each wildcard address
## to the respective list of interface addresses. As an example, if the listen address is 0.0.0.0:4001
## and the machine has 2 interfaces with IPs 172.217.11.174 and 64.233.177.113, the address mapper will
## expand the wildcard address to 172.217.11.174:4001 and 64.233.177.113:4001.
NetworkInterfaceProvider* = ref object of RootObj
proc isLoopbackOrUp(networkInterface: NetworkInterface): bool =
if (networkInterface.ifType == IfSoftwareLoopback) or
(networkInterface.state == StatusUp): true else: false
proc networkInterfaceProvider*(): NetworkInterfaceProvider =
## Returns a new instance of `NetworkInterfaceProvider`.
return NetworkInterfaceProvider()
method getAddresses*(
networkInterfaceProvider: NetworkInterfaceProvider, addrFamily: AddressFamily
): seq[InterfaceAddress] {.base.} =
## This method retrieves the addresses of network interfaces based on the specified address family.
##
## The `getAddresses` method filters the available network interfaces to include only
## those that are either loopback or up. It then collects all the addresses from these
## interfaces and filters them to match the provided address family.
##
## Parameters:
## - `networkInterfaceProvider`: A provider that offers access to network interfaces.
## - `addrFamily`: The address family to filter the network addresses (e.g., `AddressFamily.IPv4` or `AddressFamily.IPv6`).
##
## Returns:
## - A sequence of `InterfaceAddress` objects that match the specified address family.
echo "Getting addresses for address family: ", addrFamily
let
interfaces = getInterfaces().filterIt(it.isLoopbackOrUp())
flatInterfaceAddresses = concat(interfaces.mapIt(it.addresses))
filteredInterfaceAddresses =
flatInterfaceAddresses.filterIt(it.host.family == addrFamily)
return filteredInterfaceAddresses
proc new*(
T: typedesc[WildcardAddressResolverService],
networkInterfaceProvider: NetworkInterfaceProvider = new(NetworkInterfaceProvider),
): T =
## This procedure initializes a new `WildcardAddressResolverService` with the provided network interface provider.
##
## Parameters:
## - `T`: The type descriptor for `WildcardAddressResolverService`.
## - `networkInterfaceProvider`: A provider that offers access to network interfaces. Defaults to a new instance of `NetworkInterfaceProvider`.
##
## Returns:
## - A new instance of `WildcardAddressResolverService`.
return T(networkInterfaceProvider: networkInterfaceProvider)
proc getProtocolArgument*(ma: MultiAddress, codec: MultiCodec): MaResult[seq[byte]] =
var buffer: seq[byte]
for item in ma:
let
ritem = ?item
code = ?ritem.protoCode()
if code == codec:
let arg = ?ritem.protoAddress()
return ok(arg)
err("Multiaddress codec has not been found")
proc getWildcardMultiAddresses(
interfaceAddresses: seq[InterfaceAddress], protocol: Protocol, port: Port
): seq[MultiAddress] =
var addresses: seq[MultiAddress]
for ifaddr in interfaceAddresses:
var address = ifaddr.host
address.port = port
MultiAddress.init(address, protocol).withValue(maddress):
addresses.add(maddress)
addresses
proc getWildcardAddress(
maddress: MultiAddress,
multiCodec: MultiCodec,
anyAddr: openArray[uint8],
addrFamily: AddressFamily,
port: Port,
networkInterfaceProvider: NetworkInterfaceProvider,
): seq[MultiAddress] =
var addresses: seq[MultiAddress]
maddress.getProtocolArgument(multiCodec).withValue(address):
if address == anyAddr:
let filteredInterfaceAddresses = networkInterfaceProvider.getAddresses(addrFamily)
addresses.add(
getWildcardMultiAddresses(filteredInterfaceAddresses, IPPROTO_TCP, port)
)
else:
addresses.add(maddress)
return addresses
proc expandWildcardAddresses(
networkInterfaceProvider: NetworkInterfaceProvider, listenAddrs: seq[MultiAddress]
): seq[MultiAddress] =
var addresses: seq[MultiAddress]
# In this loop we expand bound addresses like `0.0.0.0` and `::` to list of interface addresses.
for listenAddr in listenAddrs:
if TCP_IP.matchPartial(listenAddr):
listenAddr.getProtocolArgument(multiCodec("tcp")).withValue(portArg):
let port = Port(uint16.fromBytesBE(portArg))
if IP4.matchPartial(listenAddr):
let wildcardAddresses = getWildcardAddress(
listenAddr,
multiCodec("ip4"),
AnyAddress.address_v4,
AddressFamily.IPv4,
port,
networkInterfaceProvider,
)
addresses.add(wildcardAddresses)
elif IP6.matchPartial(listenAddr):
let wildcardAddresses = getWildcardAddress(
listenAddr,
multiCodec("ip6"),
AnyAddress6.address_v6,
AddressFamily.IPv6,
port,
networkInterfaceProvider,
)
addresses.add(wildcardAddresses)
else:
addresses.add(listenAddr)
else:
addresses.add(listenAddr)
addresses
method setup*(
self: WildcardAddressResolverService, switch: Switch
): Future[bool] {.async.} =
## Sets up the `WildcardAddressResolverService`.
##
## This method adds the address mapper to the peer's list of address mappers.
##
## Parameters:
## - `self`: The instance of `WildcardAddressResolverService` being set up.
## - `switch`: The switch context in which the service operates.
##
## Returns:
## - A `Future[bool]` that resolves to `true` if the setup was successful, otherwise `false`.
self.addressMapper = proc(
listenAddrs: seq[MultiAddress]
): Future[seq[MultiAddress]] {.async.} =
return expandWildcardAddresses(self.networkInterfaceProvider, listenAddrs)
debug "Setting up WildcardAddressResolverService"
let hasBeenSetup = await procCall Service(self).setup(switch)
if hasBeenSetup:
switch.peerInfo.addressMappers.add(self.addressMapper)
await self.run(switch)
return hasBeenSetup
method run*(self: WildcardAddressResolverService, switch: Switch) {.async, public.} =
## Runs the WildcardAddressResolverService for a given switch.
##
## It updates the peer information for the provided switch by running the registered address mapper. Any other
## address mappers that are registered with the switch will also be run.
##
trace "Running WildcardAddressResolverService"
await switch.peerInfo.update()
method stop*(
self: WildcardAddressResolverService, switch: Switch
): Future[bool] {.async, public.} =
## Stops the WildcardAddressResolverService.
##
## Handles the shutdown process of the WildcardAddressResolverService for a given switch.
## It removes the address mapper from the switch's list of address mappers.
## It then updates the peer information for the provided switch. Any wildcard address wont be resolved anymore.
##
## Parameters:
## - `self`: The instance of the WildcardAddressResolverService.
## - `switch`: The Switch object associated with the service.
##
## Returns:
## - A future that resolves to `true` if the service was successfully stopped, otherwise `false`.
debug "Stopping WildcardAddressResolverService"
let hasBeenStopped = await procCall Service(self).stop(switch)
if hasBeenStopped:
switch.peerInfo.addressMappers.keepItIf(it != self.addressMapper)
await switch.peerInfo.update()
return hasBeenStopped

View File

@@ -112,6 +112,9 @@ template withValue*[T](self: Opt[T] | Option[T], value, body: untyped): untyped
let value {.inject.} = temp.get()
body
template withValue*[T, E](self: Result[T, E], value, body: untyped): untyped =
self.toOpt().withValue(value, body)
macro withValue*[T](self: Opt[T] | Option[T], value, body, elseStmt: untyped): untyped =
let elseBody = elseStmt[0]
quote do:

46
tests/di/testdi.nim Normal file
View File

@@ -0,0 +1,46 @@
{.used.}
# Nim-Libp2p
# Copyright (c) 2023 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.
{.push raises: [].}
import ../helpers
import ../../di/di
type
MyInterface = ref object of RootObj
MyImplementation = ref object of MyInterface
AnotherImplementation = ref object of MyInterface
MyObject = object
method doSomething(obj: MyInterface) {.base.} = discard
method doSomething(obj: MyImplementation) =
echo "MyImplementation doing something!"
method doSomething(obj: AnotherImplementation) =
echo "AnotherImplementation doing something!"
proc provideMyImplementation(): MyInterface =
MyImplementation()
proc provideAnotherImplementation(): MyInterface =
AnotherImplementation()
suite "DI":
asyncTest "DI":
let container = Container()
register[MyInterface](container, provideMyImplementation, "myImplementation")
register[MyInterface](container, provideAnotherImplementation, "anotherImplementation")
let myImplementation = resolve[MyInterface](container, "anotherImplementation")
myImplementation.doSomething()

View File

@@ -0,0 +1,86 @@
{.used.}
# Nim-Libp2p
# Copyright (c) 2024 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 stew/[byteutils]
import chronos, metrics
import unittest2
import ../libp2p/[builders, switch]
import ../libp2p/services/wildcardresolverservice
import ../libp2p/[multiaddress, multicodec]
import ./helpers
import ../di/di
type NetworkInterfaceProviderMock* = ref object of NetworkInterfaceProvider
method getAddresses(
networkInterfaceProvider: NetworkInterfaceProviderMock, addrFamily: AddressFamily
): seq[InterfaceAddress] {.gcsafe, raises: [].} =
echo "getAddressesMock"
try:
if addrFamily == AddressFamily.IPv4:
return
@[
InterfaceAddress.init(initTAddress("127.0.0.1:0"), 8),
InterfaceAddress.init(initTAddress("192.168.1.22:0"), 24),
]
else:
return
@[
InterfaceAddress.init(initTAddress("::1:0"), 8),
InterfaceAddress.init(initTAddress("fe80::1:0"), 64),
]
except TransportAddressError as e:
echo "Error: " & $e.msg
fail()
proc networkInterfaceProviderMock(): NetworkInterfaceProvider =
NetworkInterfaceProviderMock.new()
proc createSwitch(): Switch =
SwitchBuilder
.new()
.withRng(newRng())
.withAddresses(
@[
MultiAddress.init("/ip4/0.0.0.0/tcp/0/").tryGet(),
MultiAddress.init("/ip6/::/tcp/0/").tryGet(),
]
)
.withTcpTransport()
.withMplex()
.withNoise()
.withBinding(networkInterfaceProviderMock)
.build()
suite "WildcardAddressResolverService":
teardown:
checkTrackers()
asyncTest "WildcardAddressResolverService must resolve wildcard addresses and stop doing so when stopped":
let switch = createSwitch()
await switch.start()
let tcpIp4 = switch.peerInfo.addrs[0][multiCodec("tcp")].get # tcp port for ip4
let tcpIp6 = switch.peerInfo.addrs[2][multiCodec("tcp")].get # tcp port for ip6
check switch.peerInfo.addrs ==
@[
MultiAddress.init("/ip4/127.0.0.1" & $tcpIp4).get,
MultiAddress.init("/ip4/192.168.1.22" & $tcpIp4).get,
MultiAddress.init("/ip6/::1" & $tcpIp6).get,
MultiAddress.init("/ip6/fe80::1" & $tcpIp6).get,
]
await switch.stop()
check switch.peerInfo.addrs ==
@[
MultiAddress.init("/ip4/0.0.0.0" & $tcpIp4).get,
MultiAddress.init("/ip6/::" & $tcpIp6).get,
]