diff --git a/codex/codex.nim b/codex/codex.nim index b3879ab6..9bb6882c 100644 --- a/codex/codex.nim +++ b/codex/codex.nim @@ -37,7 +37,7 @@ import ./utils/addrutils import ./namespaces import ./codextypes import ./logutils -import ./nat +import ./nat/reachabilitymanager logScope: topics = "codex node" @@ -50,6 +50,7 @@ type repoStore: RepoStore maintenance: BlockMaintainer taskpool: Taskpool + reachabilityManager: ReachabilityManager isStarted: bool CodexPrivateKey* = libp2p.PrivateKey # alias @@ -75,12 +76,16 @@ proc start*(s: CodexServer) {.async.} = await s.codexNode.switch.start() - let (announceAddrs, discoveryAddrs) = nattedAddress( - s.config.nat, s.codexNode.switch.peerInfo.addrs, s.config.discoveryPort - ) + s.reachabilityManager.getAnnounceRecords = some proc() = + s.codexNode.switch.peerInfo.addrs + s.reachabilityManager.getDiscoveryRecords = some proc() = + s.codexNode.discovery.dhtRecord.data.addresses.mapIt(it.address) + s.reachabilityManager.updateAnnounceRecords = some proc(records: seq[MultiAddress]) = + s.codexNode.discovery.updateAnnounceRecord(records) + s.reachabilityManager.updateDiscoveryRecords = some proc(records: seq[MultiAddress]) = + s.codexNode.discovery.updateDhtRecord(records) - s.codexNode.discovery.updateAnnounceRecord(announceAddrs) - s.codexNode.discovery.updateDhtRecord(discoveryAddrs) + await s.reachabilityManager.start(s.codexNode.switch, s.config.bootstrapNodes) await s.codexNode.start() @@ -98,6 +103,7 @@ proc stop*(s: CodexServer) {.async.} = var futures = @[ + s.reachabilityManager.stop(), s.codexNode.switch.stop(), s.codexNode.stop(), s.codexNode.discovery.stop(), @@ -141,6 +147,9 @@ proc new*( T: type CodexServer, config: CodexConf, privateKey: CodexPrivateKey ): CodexServer = ## create CodexServer including setting up datastore, repostore, etc + + let reachabilityManager = ReachabilityManager.new(config.portMappingStrategy) + let switch = SwitchBuilder .new() .withPrivateKey(privateKey) @@ -152,6 +161,11 @@ proc new*( .withAgentVersion(config.agentString) .withSignedPeerRecord(true) .withTcpTransport({ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay}) + # Adds AutoNAT server support - ability to respond to other peers ask about their reachability status + .withAutonat() + + # Adds AutoNAT client support - to discover the node's rechability + .withServices(@[reachabilityManager.getAutonatService()]) .build() var @@ -275,4 +289,5 @@ proc new*( repoStore: repoStore, maintenance: maintenance, taskPool: taskPool, + reachabilityManager: reachabilityManager, ) diff --git a/codex/conf.nim b/codex/conf.nim index f9a80175..00122f73 100644 --- a/codex/conf.nim +++ b/codex/conf.nim @@ -41,12 +41,11 @@ import ./logutils import ./stores import ./units import ./utils -import ./nat -import ./utils/natutils +import ./nat/port_mapping from ./blockexchange/engine/pendingblocks import DefaultBlockRetries -export units, net, codextypes, logutils, completeCmdArg, parseCmdArg, NatConfig +export units, net, codextypes, logutils, completeCmdArg, parseCmdArg export DefaultQuotaBytes, DefaultBlockTtl, DefaultBlockInterval, DefaultNumBlocksPerInterval, @@ -144,14 +143,14 @@ type name: "listen-addrs" .}: seq[MultiAddress] - nat* {. + forcePortMapping* {. desc: - "Specify method to use for determining public address. " & - "Must be one of: any, none, upnp, pmp, extip:", - defaultValue: defaultNatConfig(), + "Overide automatic detection to specific upnp mode. " & + "Must be one of: any, none, upnp, pmp", + defaultValue: PortMappingStrategy.Any, defaultValueDesc: "any", - name: "nat" - .}: NatConfig + name: "force-port-mapping" + .}: PortMappingStrategy discoveryPort* {. desc: "Discovery (UDP) port", @@ -280,9 +279,6 @@ type func defaultAddress*(conf: CodexConf): IpAddress = result = static parseIpAddress("127.0.0.1") -func defaultNatConfig*(): NatConfig = - result = NatConfig(hasExtIp: false, nat: NatStrategy.NatAny) - proc getCodexVersion(): string = let tag = strip(staticExec("git describe --tags --abbrev=0")) if tag.isEmptyOrWhitespace: @@ -355,35 +351,20 @@ proc parseCmdArg*(T: type SignedPeerRecord, uri: string): T = quit QuitFailure return res.get() -func parse*(T: type NatConfig, p: string): Result[NatConfig, string] = +func parseCmdArg*(T: type PortMappingStrategy, p: string): T {.raises: [ValueError].} = case p.toLowerAscii of "any": - return ok(NatConfig(hasExtIp: false, nat: NatStrategy.NatAny)) + PortMappingStrategy.Any of "none": - return ok(NatConfig(hasExtIp: false, nat: NatStrategy.NatNone)) + PortMappingStrategy.None of "upnp": - return ok(NatConfig(hasExtIp: false, nat: NatStrategy.NatUpnp)) + PortMappingStrategy.Upnp of "pmp": - return ok(NatConfig(hasExtIp: false, nat: NatStrategy.NatPmp)) + PortMappingStrategy.Pmp else: - if p.startsWith("extip:"): - try: - let ip = parseIpAddress(p[6 ..^ 1]) - return ok(NatConfig(hasExtIp: true, extIp: ip)) - except ValueError: - let error = "Not a valid IP address: " & p[6 ..^ 1] - return err(error) - else: - return err("Not a valid NAT option: " & p) + raise newException(ValueError, "Not a valid NAT option: " & p) -proc parseCmdArg*(T: type NatConfig, p: string): T = - let res = NatConfig.parse(p) - if res.isErr: - fatal "Cannot parse the NAT config.", error = res.error(), input = p - quit QuitFailure - return res.get() - -proc completeCmdArg*(T: type NatConfig, val: string): seq[string] = +proc completeCmdArg*(T: type PortMappingStrategy, val: string): seq[string] = return @[] func parse*(T: type NBytes, p: string): Result[NBytes, string] = @@ -463,11 +444,11 @@ proc readValue*( val = dur proc readValue*( - r: var TomlReader, val: var NatConfig + r: var TomlReader, val: var PortMappingStrategy ) {.raises: [SerializationError].} = val = try: - parseCmdArg(NatConfig, r.readValue(string)) + parseCmdArg(PortMappingStrategy, r.readValue(string)) except CatchableError as err: raise newException(SerializationError, err.msg) diff --git a/codex/nat.nim b/codex/nat.nim deleted file mode 100644 index 2038ca3d..00000000 --- a/codex/nat.nim +++ /dev/null @@ -1,432 +0,0 @@ -# Copyright (c) 2019-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 - std/[options, os, strutils, times, net, atomics], - stew/[objects], - nat_traversal/[miniupnpc, natpmp], - json_serialization/std/net, - results - -import pkg/chronos -import pkg/chronicles -import pkg/libp2p - -import ./utils -import ./utils/natutils -import ./utils/addrutils - -const - UPNP_TIMEOUT = 200 # ms - PORT_MAPPING_INTERVAL = 20 * 60 # seconds - NATPMP_LIFETIME = 60 * 60 # in seconds, must be longer than PORT_MAPPING_INTERVAL - -type PortMappings* = object - internalTcpPort: Port - externalTcpPort: Port - internalUdpPort: Port - externalUdpPort: Port - description: string - -type PortMappingArgs = - tuple[strategy: NatStrategy, tcpPort, udpPort: Port, description: string] - -type NatConfig* = object - case hasExtIp*: bool - of true: extIp*: IpAddress - of false: nat*: NatStrategy - -var - upnp {.threadvar.}: Miniupnp - npmp {.threadvar.}: NatPmp - strategy = NatStrategy.NatNone - natClosed: Atomic[bool] - extIp: Option[IpAddress] - activeMappings: seq[PortMappings] - natThreads: seq[Thread[PortMappingArgs]] = @[] - -logScope: - topics = "nat" - -type PrefSrcStatus = enum - NoRoutingInfo - PrefSrcIsPublic - PrefSrcIsPrivate - BindAddressIsPublic - BindAddressIsPrivate - -## Also does threadvar initialisation. -## Must be called before redirectPorts() in each thread. -proc getExternalIP*(natStrategy: NatStrategy, quiet = false): Option[IpAddress] = - var externalIP: IpAddress - - if natStrategy == NatStrategy.NatAny or natStrategy == NatStrategy.NatUpnp: - if upnp == nil: - upnp = newMiniupnp() - - upnp.discoverDelay = UPNP_TIMEOUT - let dres = upnp.discover() - if dres.isErr: - debug "UPnP", msg = dres.error - else: - var - msg: cstring - canContinue = true - case upnp.selectIGD() - of IGDNotFound: - msg = "Internet Gateway Device not found. Giving up." - canContinue = false - of IGDFound: - msg = "Internet Gateway Device found." - of IGDNotConnected: - msg = "Internet Gateway Device found but it's not connected. Trying anyway." - of NotAnIGD: - msg = - "Some device found, but it's not recognised as an Internet Gateway Device. Trying anyway." - of IGDIpNotRoutable: - msg = - "Internet Gateway Device found and is connected, but with a reserved or non-routable IP. Trying anyway." - if not quiet: - debug "UPnP", msg - if canContinue: - let ires = upnp.externalIPAddress() - if ires.isErr: - debug "UPnP", msg = ires.error - else: - # if we got this far, UPnP is working and we don't need to try NAT-PMP - try: - externalIP = parseIpAddress(ires.value) - strategy = NatStrategy.NatUpnp - return some(externalIP) - except ValueError as e: - error "parseIpAddress() exception", err = e.msg - return - - if natStrategy == NatStrategy.NatAny or natStrategy == NatStrategy.NatPmp: - if npmp == nil: - npmp = newNatPmp() - let nres = npmp.init() - if nres.isErr: - debug "NAT-PMP", msg = nres.error - else: - let nires = npmp.externalIPAddress() - if nires.isErr: - debug "NAT-PMP", msg = nires.error - else: - try: - externalIP = parseIpAddress($(nires.value)) - strategy = NatStrategy.NatPmp - return some(externalIP) - except ValueError as e: - error "parseIpAddress() exception", err = e.msg - return - -# This queries the routing table to get the "preferred source" attribute and -# checks if it's a public IP. If so, then it's our public IP. -# -# Further more, we check if the bind address (user provided, or a "0.0.0.0" -# default) is a public IP. That's a long shot, because code paths involving a -# user-provided bind address are not supposed to get here. -proc getRoutePrefSrc(bindIp: IpAddress): (Option[IpAddress], PrefSrcStatus) = - let bindAddress = initTAddress(bindIp, Port(0)) - - if bindAddress.isAnyLocal(): - let ip = getRouteIpv4() - if ip.isErr(): - # No route was found, log error and continue without IP. - error "No routable IP address found, check your network connection", - error = ip.error - return (none(IpAddress), NoRoutingInfo) - elif ip.get().isGlobalUnicast(): - return (some(ip.get()), PrefSrcIsPublic) - else: - return (none(IpAddress), PrefSrcIsPrivate) - elif bindAddress.isGlobalUnicast(): - return (some(bindIp), BindAddressIsPublic) - else: - return (none(IpAddress), BindAddressIsPrivate) - -# Try to detect a public IP assigned to this host, before trying NAT traversal. -proc getPublicRoutePrefSrcOrExternalIP*( - natStrategy: NatStrategy, bindIp: IpAddress, quiet = true -): Option[IpAddress] = - let (prefSrcIp, prefSrcStatus) = getRoutePrefSrc(bindIp) - - case prefSrcStatus - of NoRoutingInfo, PrefSrcIsPublic, BindAddressIsPublic: - return prefSrcIp - of PrefSrcIsPrivate, BindAddressIsPrivate: - let extIp = getExternalIP(natStrategy, quiet) - if extIp.isSome: - return some(extIp.get) - -proc doPortMapping( - strategy: NatStrategy, tcpPort, udpPort: Port, description: string -): Option[(Port, Port)] {.gcsafe.} = - var - extTcpPort: Port - extUdpPort: Port - - if strategy == NatStrategy.NatUpnp: - for t in [(tcpPort, UPNPProtocol.TCP), (udpPort, UPNPProtocol.UDP)]: - let - (port, protocol) = t - pmres = upnp.addPortMapping( - externalPort = $port, - protocol = protocol, - internalHost = upnp.lanAddr, - internalPort = $port, - desc = description, - leaseDuration = 0, - ) - if pmres.isErr: - error "UPnP port mapping", msg = pmres.error, port - return - else: - # let's check it - let cres = - upnp.getSpecificPortMapping(externalPort = $port, protocol = protocol) - if cres.isErr: - warn "UPnP port mapping check failed. Assuming the check itself is broken and the port mapping was done.", - msg = cres.error - - info "UPnP: added port mapping", - externalPort = port, internalPort = port, protocol = protocol - case protocol - of UPNPProtocol.TCP: - extTcpPort = port - of UPNPProtocol.UDP: - extUdpPort = port - elif strategy == NatStrategy.NatPmp: - for t in [(tcpPort, NatPmpProtocol.TCP), (udpPort, NatPmpProtocol.UDP)]: - let - (port, protocol) = t - pmres = npmp.addPortMapping( - eport = port.cushort, - iport = port.cushort, - protocol = protocol, - lifetime = NATPMP_LIFETIME, - ) - if pmres.isErr: - error "NAT-PMP port mapping", msg = pmres.error, port - return - else: - let extPort = Port(pmres.value) - info "NAT-PMP: added port mapping", - externalPort = extPort, internalPort = port, protocol = protocol - case protocol - of NatPmpProtocol.TCP: - extTcpPort = extPort - of NatPmpProtocol.UDP: - extUdpPort = extPort - return some((extTcpPort, extUdpPort)) - -proc repeatPortMapping(args: PortMappingArgs) {.thread, raises: [ValueError].} = - ignoreSignalsInThread() - let - (strategy, tcpPort, udpPort, description) = args - interval = initDuration(seconds = PORT_MAPPING_INTERVAL) - sleepDuration = 1_000 # in ms, also the maximum delay after pressing Ctrl-C - - var lastUpdate = now() - - # We can't use copies of Miniupnp and NatPmp objects in this thread, because they share - # C pointers with other instances that have already been garbage collected, so - # we use threadvars instead and initialise them again with getExternalIP(), - # even though we don't need the external IP's value. - let ipres = getExternalIP(strategy, quiet = true) - if ipres.isSome: - while natClosed.load() == false: - let - # we're being silly here with this channel polling because we can't - # select on Nim channels like on Go ones - currTime = now() - if currTime >= (lastUpdate + interval): - discard doPortMapping(strategy, tcpPort, udpPort, description) - lastUpdate = currTime - - sleep(sleepDuration) - -proc stopNatThreads() {.noconv.} = - # stop the thread - debug "Stopping NAT port mapping renewal threads" - try: - natClosed.store(true) - joinThreads(natThreads) - except Exception as exc: - warn "Failed to stop NAT port mapping renewal thread", exc = exc.msg - - # delete our port mappings - - # FIXME: if the initial port mapping failed because it already existed for the - # required external port, we should not delete it. It might have been set up - # by another program. - - # In Windows, a new thread is created for the signal handler, so we need to - # initialise our threadvars again. - - let ipres = getExternalIP(strategy, quiet = true) - if ipres.isSome: - if strategy == NatStrategy.NatUpnp: - for entry in activeMappings: - for t in [ - (entry.externalTcpPort, entry.internalTcpPort, UPNPProtocol.TCP), - (entry.externalUdpPort, entry.internalUdpPort, UPNPProtocol.UDP), - ]: - let - (eport, iport, protocol) = t - pmres = upnp.deletePortMapping(externalPort = $eport, protocol = protocol) - if pmres.isErr: - error "UPnP port mapping deletion", msg = pmres.error - else: - debug "UPnP: deleted port mapping", - externalPort = eport, internalPort = iport, protocol = protocol - elif strategy == NatStrategy.NatPmp: - for entry in activeMappings: - for t in [ - (entry.externalTcpPort, entry.internalTcpPort, NatPmpProtocol.TCP), - (entry.externalUdpPort, entry.internalUdpPort, NatPmpProtocol.UDP), - ]: - let - (eport, iport, protocol) = t - pmres = npmp.deletePortMapping( - eport = eport.cushort, iport = iport.cushort, protocol = protocol - ) - if pmres.isErr: - error "NAT-PMP port mapping deletion", msg = pmres.error - else: - debug "NAT-PMP: deleted port mapping", - externalPort = eport, internalPort = iport, protocol = protocol - -proc redirectPorts*( - strategy: NatStrategy, tcpPort, udpPort: Port, description: string -): Option[(Port, Port)] = - result = doPortMapping(strategy, tcpPort, udpPort, description) - if result.isSome: - let (externalTcpPort, externalUdpPort) = result.get() - # needed by NAT-PMP on port mapping deletion - # Port mapping works. Let's launch a thread that repeats it, in case the - # NAT-PMP lease expires or the router is rebooted and forgets all about - # these mappings. - activeMappings.add( - PortMappings( - internalTcpPort: tcpPort, - externalTcpPort: externalTcpPort, - internalUdpPort: udpPort, - externalUdpPort: externalUdpPort, - description: description, - ) - ) - try: - natThreads.add(Thread[PortMappingArgs]()) - natThreads[^1].createThread( - repeatPortMapping, (strategy, externalTcpPort, externalUdpPort, description) - ) - # atexit() in disguise - if natThreads.len == 1: - # we should register the thread termination function only once - addQuitProc(stopNatThreads) - except Exception as exc: - warn "Failed to create NAT port mapping renewal thread", exc = exc.msg - -proc setupNat*( - natStrategy: NatStrategy, tcpPort, udpPort: Port, clientId: string -): tuple[ip: Option[IpAddress], tcpPort, udpPort: Option[Port]] = - ## Setup NAT port mapping and get external IP address. - ## If any of this fails, we don't return any IP address but do return the - ## original ports as best effort. - ## TODO: Allow for tcp or udp port mapping to be optional. - if extIp.isNone: - extIp = getExternalIP(natStrategy) - if extIp.isSome: - let ip = extIp.get - let extPorts = ( - {.gcsafe.}: - redirectPorts( - strategy, tcpPort = tcpPort, udpPort = udpPort, description = clientId - ) - ) - if extPorts.isSome: - let (extTcpPort, extUdpPort) = extPorts.get() - (ip: some(ip), tcpPort: some(extTcpPort), udpPort: some(extUdpPort)) - else: - warn "UPnP/NAT-PMP available but port forwarding failed" - (ip: none(IpAddress), tcpPort: some(tcpPort), udpPort: some(udpPort)) - else: - warn "UPnP/NAT-PMP not available" - (ip: none(IpAddress), tcpPort: some(tcpPort), udpPort: some(udpPort)) - -proc setupAddress*( - natConfig: NatConfig, bindIp: IpAddress, tcpPort, udpPort: Port, clientId: string -): tuple[ip: Option[IpAddress], tcpPort, udpPort: Option[Port]] {.gcsafe.} = - ## Set-up of the external address via any of the ways as configured in - ## `NatConfig`. In case all fails an error is logged and the bind ports are - ## selected also as external ports, as best effort and in hope that the - ## external IP can be figured out by other means at a later stage. - ## TODO: Allow for tcp or udp bind ports to be optional. - - if natConfig.hasExtIp: - # any required port redirection must be done by hand - return (some(natConfig.extIp), some(tcpPort), some(udpPort)) - - case natConfig.nat - of NatStrategy.NatAny: - let (prefSrcIp, prefSrcStatus) = getRoutePrefSrc(bindIp) - - case prefSrcStatus - of NoRoutingInfo, PrefSrcIsPublic, BindAddressIsPublic: - return (prefSrcIp, some(tcpPort), some(udpPort)) - of PrefSrcIsPrivate, BindAddressIsPrivate: - return setupNat(natConfig.nat, tcpPort, udpPort, clientId) - of NatStrategy.NatNone: - let (prefSrcIp, prefSrcStatus) = getRoutePrefSrc(bindIp) - - case prefSrcStatus - of NoRoutingInfo, PrefSrcIsPublic, BindAddressIsPublic: - return (prefSrcIp, some(tcpPort), some(udpPort)) - of PrefSrcIsPrivate: - error "No public IP address found. Should not use --nat:none option" - return (none(IpAddress), some(tcpPort), some(udpPort)) - of BindAddressIsPrivate: - error "Bind IP is not a public IP address. Should not use --nat:none option" - return (none(IpAddress), some(tcpPort), some(udpPort)) - of NatStrategy.NatUpnp, NatStrategy.NatPmp: - return setupNat(natConfig.nat, tcpPort, udpPort, clientId) - -proc nattedAddress*( - natConfig: NatConfig, addrs: seq[MultiAddress], udpPort: Port -): tuple[libp2p, discovery: seq[MultiAddress]] = - ## Takes a NAT configuration, sequence of multiaddresses and UDP port and returns: - ## - Modified multiaddresses with NAT-mapped addresses for libp2p - ## - Discovery addresses with NAT-mapped UDP ports - - var discoveryAddrs = newSeq[MultiAddress](0) - let newAddrs = addrs.mapIt: - block: - # Extract IP address and port from the multiaddress - let (ipPart, port) = getAddressAndPort(it) - if ipPart.isSome and port.isSome: - # Try to setup NAT mapping for the address - let (newIP, tcp, udp) = - setupAddress(natConfig, ipPart.get, port.get, udpPort, "codex") - if newIP.isSome: - # NAT mapping successful - add discovery address with mapped UDP port - discoveryAddrs.add(getMultiAddrWithIPAndUDPPort(newIP.get, udp.get)) - # Remap original address with NAT IP and TCP port - it.remapAddr(ip = newIP, port = tcp) - else: - # NAT mapping failed - use original address - echo "Failed to get external IP, using original address", it - discoveryAddrs.add(getMultiAddrWithIPAndUDPPort(ipPart.get, udpPort)) - it - else: - # Invalid multiaddress format - return as is - it - (newAddrs, discoveryAddrs) diff --git a/codex/nat/port_mapping.nim b/codex/nat/port_mapping.nim new file mode 100644 index 00000000..f0222af6 --- /dev/null +++ b/codex/nat/port_mapping.nim @@ -0,0 +1,363 @@ +# Copyright (c) 2019-2025 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/[options, os, strutils, times, net, atomics] + +import pkg/stew/objects +import pkg/nat_traversal/[miniupnpc, natpmp] +import pkg/json_serialization/std/net +import pkg/results +import pkg/questionable +import pkg/questionable/results +import pkg/chronos +import pkg/chronicles +import pkg/libp2p + +import ../utils + +logScope: + topics = "codex nat port-mapping" + +const + UPNP_TIMEOUT = 200 # ms + RENEWAL_INTERVAL = 20 * 60 # seconds + Pmp_LIFETIME = 60 * 60 # in seconds, must be longer than RENEWAL_INTERVAL + MAPPING_DESCRIPTION = "codex" + +type PortMappingStrategy* = enum + Any + Upnp + Pmp + None + +type MappingPort* = ref object of RootObj + value*: Port + +type TcpPort* = ref object of MappingPort +type UdpPort* = ref object of MappingPort + +proc newTcpMappingPort*(value: Port): TcpPort = + TcpPort(value: value) + +proc newUdpMappingPort*(value: Port): UdpPort = + UdpPort(value: value) + +type PortMapping = tuple[internalPort: MappingPort, externalPort: Option[MappingPort]] +type RenewelThreadArgs = + tuple[strategy: PortMappingStrategy, portMapping: seq[PortMapping]] + +var + upnp {.threadvar.}: Miniupnp + npmp {.threadvar.}: NatPmp + mappings: seq[PortMapping] + portMappingExiting: Atomic[bool] + renewalThread: Thread[RenewelThreadArgs] + +proc initUpnp(): bool = + logScope: + protocol = "upnp" + + if upnp != nil: + warn "UPnP already initialized!" + return true + + upnp = newMiniupnp() + upnp.discoverDelay = UPNP_TIMEOUT + + if err =? upnp.discover().errorOption: + warn "UPnP error discoverning Internet Gateway Devices", msg = err + upnp = nil + return false + + case upnp.selectIGD() + of IGDNotFound: + info "UPnP Internet Gateway Device not found. Giving up." + upnp = nil + # As UPnP is not supported on our network we won't be using it --> lets erase it. + of IGDFound: + info "UPnP Internet Gateway Device found." + of IGDNotConnected: + info "UPnP Internet Gateway Device found but it's not connected. Trying anyway." + of NotAnIGD: + info "Some device found, but it's not recognised as an Internet Gateway Device. Trying anyway." + of IGDIpNotRoutable: + info "UPnP Internet Gateway Device found and is connected, but with a reserved or non-routable IP. Trying anyway." + + return true + +proc initNpmp(): bool = + logScope: + protocol = "npmp" + + if npmp != nil: + warn "NAT-PMP already initialized!" + return true + + npmp = newNatPmp() + + if err =? npmp.init().errorOption: + warn "Error initialization of NAT-PMP", msg = err + npmp = nil + return false + + if err =? npmp.externalIPAddress().errorOption: + warn "Fetching of external IP failed.", msg = err + npmp = nil + return false + + info "NAT-PMP initialized" + return true + +## Try to initilize all the port mapping protocols and returns +## the protocol that will be used. +proc initProtocols(strategy: PortMappingStrategy): PortMappingStrategy = + if strategy == PortMappingStrategy.Any or strategy == PortMappingStrategy.Upnp: + if initUpnp(): + return PortMappingStrategy.Upnp + + if strategy == PortMappingStrategy.Any or strategy == PortMappingStrategy.Pmp: + if initNpmp(): + return PortMappingStrategy.Pmp + + return PortMappingStrategy.None + +proc upnpPortMapping( + internalPort: MappingPort, externalPort: MappingPort +): ?!MappingPort {.gcsafe.} = + let protocol = if (internalPort is TcpPort): UPNPProtocol.TCP else: UPNPProtocol.UDP + + logScope: + protocol = "upnp" + externalPort = externalPort.value + internalPort = internalPort.value + protocol = protocol + + let pmres = upnp.addPortMapping( + externalPort = $(externalPort.value), + protocol = protocol, + internalHost = upnp.lanAddr, + internalPort = $(internalPort.value), + desc = MAPPING_DESCRIPTION, + leaseDuration = 0, + ) + + if pmres.isErr: + error "UPnP port mapping", msg = pmres.error + return failure(pmres.error) + + # let's check it + let cres = upnp.getSpecificPortMapping( + externalPort = $(externalPort.value), protocol = protocol + ) + if cres.isErr: + warn "UPnP port mapping check failed. Assuming the check itself is broken and the port mapping was done.", + msg = cres.error + info "UPnP added port mapping" + + return success(externalPort) + +proc npmpPortMapping( + internalPort: MappingPort, externalPort: MappingPort +): ?!MappingPort {.gcsafe.} = + let protocol = + if (internalPort is TcpPort): NatPmpProtocol.TCP else: NatPmpProtocol.UDP + + logScope: + protocol = "npmp" + externalPort = externalPort.value + internalPort = internalPort.value + protocol = protocol + + without extPort =? + npmp.addPortMapping( + eport = externalPort.value, + iport = internalPort.value, + protocol = protocol, + lifetime = Pmp_LIFETIME, + ), err: + error "NAT-PMP port mapping error", msg = err.msg + return failure(err.msg) + + info "NAT-PMP: added port mapping" + + if internalPort is TcpPort: + return success(newTcpMappingPort(extPort)) + else: + return success(newUdpMappingPort(extPort)) + +## Create port mapping that will try to utilize the same port number +## of the internal port for the external port mapping. +## +## TODO: Add support for trying mapping of random external port. + +proc doPortMapping(port: MappingPort): ?!MappingPort {.gcsafe.} = + if upnp != nil: + return upnpPortMapping(port, port) + + if npmp != nil: + return npmpPortMapping(port, port) + + return failure("No active startegy") + +proc doPortMapping( + internalPort: MappingPort, externalPort: MappingPort +): ?!MappingPort {.gcsafe.} = + if upnp != nil: + return upnpPortMapping(internalPort, externalPort) + + if npmp != nil: + return npmpPortMapping(internalPort, externalPort) + + return failure("No active startegy") + +## Gets external IP provided by the port mapping protocols +## Port mapping needs to be succesfully started first using `startPortMapping()` +proc getExternalIP*(): ?IpAddress = + if upnp == nil and npmp == nil: + warn "No available port-mapping protocol" + return IpAddress.none + + if upnp != nil: + let ires = upnp.externalIPAddress + if ires.isOk(): + info "Got externa IP address: " & ires.value, ip = ires.value + return parseIpAddress(ires.value).some + else: + debug "Getting external IP address using UPnP failed", + msg = ires.error, protocol = "upnp" + + if npmp != nil: + let nires = npmp.externalIPAddress() + if nires.isErr: + debug "Getting external IP address using NAT-PMP failed", msg = nires.error + else: + try: + info "Got externa IP address: " & $(nires.value), + ip =$ (nires.value), protocol = "npmp" + return parseIpAddress($(nires.value)).some + except ValueError as e: + error "parseIpAddress() exception", err = e.msg + + return IpAddress.none + +proc startPortMapping*( + strategy: PortMappingStrategy, internalPorts: seq[MappingPort] +): ?!seq[PortMapping] = + if strategy == PortMappingStrategy.None: + return failure("No port mapping strategy requested") + + if internalPorts.len == 0: + return failure("No internal ports to be mapped were supplied") + + strategy = initProtocols(strategy) + if strategy == PortMappingStrategy.None: + return failure("No available port mapping protocols on the network") + + portMapping = newSeqOfCap[PortMappings](internalPorts.len) + + for port in internalPorts: + without mappedPort =? doPortMapping(port), err: + warn "Failed to map port", port = port.value, msg = err.msg + portMapping.add((internalPort: port, externalPort: MappingPort.none)) + + portMapping.add((internalPort: port, externalPort: mappedPort.some)) + + startRenewalThread(strategy) + + return success(externalPorts) + +proc stopPortMapping*() = + if upnp == nil or npmp == nil: + debug "Port mapping is not running, nothing to stop" + return + + info "Stopping port mapping renewal threads" + try: + portMappingExiting.store(true) + renewalThread.join() + except CatchableError as exc: + warn "Failed to stop port mapping renewal thread", exc = exc.msg + + for mapping in portMapping: + if upnp != nil: + let protocol = + if (internalPort is TcpPort): UPNPProtocol.TCP else: UPNPProtocol.UDP + + if err =? + upnp.deletePortMapping( + externalPort = $(mapping.externalPort.value), protocol = protocol + ).errorOption: + error "UPnP port mapping deletion error", msg = err.msg + else: + debug "UPnP: deleted port mapping", + externalPort = mapping.externalPort, + internalPort = mapping.internalPort, + protocol = protocol + + if npnp != nil: + let protocol = + if (internalPort is TcpPort): NatPmpProtocol.TCP else: NatPmpProtocol.UDP + + if err =? + npmp.deletePortMapping( + eport = mapping.externalPort.value, + iport = mapping.internalPort.value, + protocol = protocol, + ).errorOption: + error "NAT-PMP port mapping deletion error", msg = err.msg + else: + debug "NAT-PMP: deleted port mapping", + externalPort = mapping.externalPort, + internalPort = mapping.internalPort, + protocol = protocol + +proc startRenewalThread( + strategy: PortMappingStrategy, + internalPorts: seq[MappingPort], + externalPorts: seq[?MappingPort], +) = + try: + renewalThread = Thread[RenewelThreadArgs]() + renewalThread.createThread(renewPortMapping, (strategy, portMapping)) + except CatchableError as exc: + warn "Failed to create NAT port mapping renewal thread", exc = exc.msg + +proc renewPortMapping(args: RenewelThreadArgs) {.thread, raises: [ValueError].} = + ignoreSignalsInThread() + let + (strategy, portMappings) = args + interval = initDuration(seconds = RENEWAL_INTERVAL) + sleepDuration = 1_000 # in ms, also the maximum delay after pressing Ctrl-C + + var lastUpdate = now() + + # We can't use copies of Miniupnp and Pmp objects in this thread, because they share + # C pointers with other instances that have already been garbage collected, so + # we use threadvars instead and initialise them again with initProtocols(), + # even though we don't need the external IP's value. + + if initProtocols(strategy) == PortMappingStrategy.None: + error "Could not initiate protocols in renewal thread" + return + + while portMappingExiting.load() == false: + if now() >= (lastUpdate + interval): + for mapping in portMappings: + if externalPort =? mapping.externalPort: + without renewedExternalPort =? + doPortMapping(mapping.internalPort, externalPort), err: + error "Error while renewal of port mapping", msg = err.msg + + if renewedExternalPort.value != externalPort.value: + error "The renewed external port is not the same as the originally mapped" + + lastUpdate = now() + + sleep(sleepDuration) diff --git a/codex/nat/reachabilitymanager.nim b/codex/nat/reachabilitymanager.nim new file mode 100644 index 00000000..b110b45e --- /dev/null +++ b/codex/nat/reachabilitymanager.nim @@ -0,0 +1,153 @@ +import std/sequtils + +import pkg/chronos +import pkg/chronicles +import pkg/questionable +import pkg/questionable/results +import pkg/libp2p +import pkg/libp2p/protocols/connectivity/autonat/client +import pkg/libp2p/protocols/connectivity/autonat/service + +import ../rng as random +import ./port_mapping + +const AutonatCheckInterval = Opt.some(chronos.seconds(30)) + +logScope: + topics = "codex nat reachabilitymanager" + +type + ReachabilityManager* = ref object of RootObj + networkReachability*: NetworkReachability + portMappingStrategy: PortMappingStrategy + getAnnounceRecords*: ?GetRecords + getDiscoveryRecords*: ?GetRecords + updateAnnounceRecords*: ?UpdateRecords + updateDiscoveryRecords*: ?UpdateRecords + started = false + + GetRecords* = proc(): seq[MultiAddress] {.raises: [].} + UpdateRecords* = proc(records: seq[MultiAddress]) {.raises: [].} + +proc new*( + T: typedesc[ReachabilityManager], portMappingStrategy: PortMappingStrategy +): T = + return T(portMappingStrategy: portMappingStrategy) + +proc getReachabilityHandler(manager: ReachabilityManager): StatusAndConfidenceHandler = + let statusAndConfidenceHandler = proc( + networkReachability: NetworkReachability, confidenceOpt: Opt[float] + ): Future[void] {.gcsafe, async: (raises: [CancelledError]).} = + if not started: + warn "ReachabilityManager was not started, but we are already getting reachability updates! Ignoring..." + return + + without confidence =? confidenceOpt: + debug "Node reachability reported without confidence" + return + + if manager.networkReachability == networkReachability: + debug "Node reachability reported without change", + networkReachability = networkReachability + return + + info "Node reachability status changed", + networkReachability = networkReachability, confidence = confidenceOpt + + manager.networkReachability = networkReachability + + if networkReachability == NetworkReachability.Unreachable: + # Lets first start to expose port using port mapping protocols like NAT-PMP or UPnP + if manager.startPortMapping(): + return # We exposed ports so we should be good! + + info "No more options to become reachable" + + return statusAndConfidenceHandler + +proc startPortMapping(self: ReachabilityManager): bool = + try: + let announceRecords = self.getAnnounceRecords() + let discoveryRecords = self.getDiscoveryRecords() + let portsToBeMapped = + (announceRecords & discoveryRecords).mapIt(getAddressAndPort(it)).mapIt(it.port) + + without mappedPorts =? startPortMapping( + manager.portMappingStrategy, portsToBeMapped + ), err: + warn "Could not start port mapping", msg = err + return false + + if mappedPorts.any( + proc(x: ?MappingPort): bool = + isNone(x) + ): + warn "Some ports were not mapped - not using port mapping then" + return false + + info "Started port mapping" + + let announceMappedRecords = zip( + announceRecords, mappedPorts[0 .. announceRecords.len - 1] + ) + .mapIt(getMultiAddr(getAddressAndPort(it[0]).ip, it[1].value)) + self.updateAnnounceRecords(announceMappedRecords) + + let discoveryMappedRecords = zip( + discoveryRecords, mappedPorts[announceRecords.len, ^1] + ) + .mapIt(getMultiAddr(getAddressAndPort(it[0]).ip, it[1].value)) + self.updateDiscoveryRecords(discoveryMappedRecords) + + return true + except ValueError as exc: + error "Error while starting port mapping", msg = exc.msg + return false + +proc start*( + self: ReachabilityManager, switch: Switch, bootNodes: seq[SignedPeerRecord] +): Future[void] {.async: (raises: [CancelledError]).} = + doAssert self.getAnnounceRecords.isSome, "getAnnounceRecords is not set" + doAssert self.getDiscoveryRecords.isSome, "getDiscoveryRecords is not set" + doAssert self.updateAnnounceRecords.isSome, "updateAnnounceRecords is not set" + doAssert self.updateDiscoveryRecords.isSome, "updateDiscoveryRecords is not set" + self.started = true + + ## Until more robust way of NAT-traversal helper peers discovery is implemented + ## we will start with simple populating the libp2p peerstore with bootstrap nodes + ## https://github.com/codex-storage/nim-codex/issues/1320 + + for peer in bootNodes: + try: + await switch.connect(peer.data.peerId, peer.data.addresses.mapIt(it.address)) + except CancelledError as exc: + raise exc + except CatchableError as exc: + info "Failed to dial bootstrap nodes", err = exc.msg + +proc stop*(): Future[void] {.async: (raises: [CancelledError]).} = + stopPortMapping() + self.started = false + +proc getAutonatService*(self: ReachabilityManager): Service = + ## AutonatService request other peers to dial us back + ## flagging us as Reachable or NotReachable. + ## We use minimum confidence 0.1 (confidence is calculated as numOfReplies/maxQueueSize) as + ## that will give an answer already for response from one peer. + ## As we use bootnodes for this in initial setup, it is possible we might + ## get only one peer to ask about our reachability and it is crucial to get at least some reply. + ## This should be changed once proactive NAT-traversal helper peers discovery is implemented. + + let autonatService = AutonatService.new( + autonatClient = AutonatClient.new(), + rng = random.Rng.instance(), + scheduleInterval = AutonatCheckInterval, + askNewConnectedPeers = true, + numPeersToAsk = 5, + maxQueueSize = 10, + minConfidence = 0.1, + ) + + autonatService.statusAndConfidenceHandler(self.getReachabilityHandler()) + + return Service(autonatService) diff --git a/codex/nat/utils.nim b/codex/nat/utils.nim new file mode 100644 index 00000000..ae856d31 --- /dev/null +++ b/codex/nat/utils.nim @@ -0,0 +1,68 @@ +## Nim-Codex +## Copyright (c) 2022 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/strutils +import std/options +import std/net + +import pkg/libp2p +import pkg/questionable +import pkg/questionable/results + +import ./port_mapping + +proc getAddressAndPort*( + ma: MultiAddress +): tuple[ip: IpAddress, port: MappingPort] {.raises: [ValueError].} = + try: + # Try IPv4 first + let ipv4Result = ma[multiCodec("ip4")] + let ip = + if ipv4Result.isOk: + let ipBytes = ipv4Result.get().protoArgument().expect("Invalid IPv4 format") + let ipArray = [ipBytes[0], ipBytes[1], ipBytes[2], ipBytes[3]] + IpAddress(family: IPv4, address_v4: ipArray) + else: + # Try IPv6 if IPv4 not found + let ipv6Result = ma[multiCodec("ip6")] + if ipv6Result.isOk: + let ipBytes = ipv6Result.get().protoArgument().expect("Invalid IPv6 format") + var ipArray: array[16, byte] + for i in 0 .. 15: + ipArray[i] = ipBytes[i] + IpAddress(family: IPv6, address_v6: ipArray) + else: + raise newException(ValueError, "Unknown IP family") + + # Get TCP Port + let tcpPortResult = ma[multiCodec("tcp")] + if tcpPortResult.isOk: + let tcpPortBytes = + tcpPortResult.get().protoArgument().expect("Invalid port format") + let tcpPort = newTcpMappingPort(Port(fromBytesBE(uint16, tcpPortBytes))) + return (ip: ip, port: tcpPort) + + # Get UDP Port + let udpPortResult = ma[multiCodec("udp")] + if udpPortResult.isOk: + let udpPortBytes = + udpPortResult.get().protoArgument().expect("Invalid port format") + let udpPort = newUdpMappingPort(Port(fromBytesBE(uint16, udpPortBytes))) + return (ip: ip, port: udpPort) + + raise newException(ValueError, "No TCP/UDP port specified") + except Exception: + raise newException(ValueError, "Invalid multiaddr") + +proc getMultiAddr*(ip: IpAddress, port: MappingPort): MultiAddress = + let ipFamily = if ip.family == IpAddressFamily.IPv4: "/ip4/" else: "/ip6/" + let portType = if (internalPort is TcpPort): "/tcp/" else: "/udp/" + return MultiAddress.init(ipFamily & $ip & portType & $(port.value)).expect( + "valid multiaddr" + ) diff --git a/codex/utils/addrutils.nim b/codex/utils/addrutils.nim index 1b7a556a..e65de1ed 100644 --- a/codex/utils/addrutils.nim +++ b/codex/utils/addrutils.nim @@ -12,6 +12,7 @@ import std/net import std/strutils import std/options +import std/net import pkg/libp2p import pkg/stew/endians2 @@ -42,7 +43,7 @@ func remapAddr*( proc getMultiAddrWithIPAndUDPPort*(ip: IpAddress, port: Port): MultiAddress = ## Creates a MultiAddress with the specified IP address and UDP port - ## + ## ## Parameters: ## - ip: A valid IP address (IPv4 or IPv6) ## - port: The UDP port number diff --git a/codex/utils/natutils.nim b/codex/utils/natutils.nim deleted file mode 100644 index 45ad7589..00000000 --- a/codex/utils/natutils.nim +++ /dev/null @@ -1,66 +0,0 @@ -{.push raises: [].} - -import std/[net, tables, hashes], pkg/results, chronos, chronicles - -import pkg/libp2p - -type NatStrategy* = enum - NatAny - NatUpnp - NatPmp - NatNone - -type IpLimits* = object - limit*: uint - ips: Table[IpAddress, uint] - -func hash*(ip: IpAddress): Hash = - case ip.family - of IpAddressFamily.IPv6: - hash(ip.address_v6) - of IpAddressFamily.IPv4: - hash(ip.address_v4) - -func inc*(ipLimits: var IpLimits, ip: IpAddress): bool = - let val = ipLimits.ips.getOrDefault(ip, 0) - if val < ipLimits.limit: - ipLimits.ips[ip] = val + 1 - true - else: - false - -func dec*(ipLimits: var IpLimits, ip: IpAddress) = - let val = ipLimits.ips.getOrDefault(ip, 0) - if val == 1: - ipLimits.ips.del(ip) - elif val > 1: - ipLimits.ips[ip] = val - 1 - -func isGlobalUnicast*(address: TransportAddress): bool = - if address.isGlobal() and address.isUnicast(): true else: false - -func isGlobalUnicast*(address: IpAddress): bool = - let a = initTAddress(address, Port(0)) - a.isGlobalUnicast() - -proc getRouteIpv4*(): Result[IpAddress, cstring] = - # Avoiding Exception with initTAddress and can't make it work with static. - # Note: `publicAddress` is only used an "example" IP to find the best route, - # no data is send over the network to this IP! - let - publicAddress = TransportAddress( - family: AddressFamily.IPv4, address_v4: [1'u8, 1, 1, 1], port: Port(0) - ) - route = getBestRoute(publicAddress) - - if route.source.isUnspecified(): - err("No best ipv4 route found") - else: - let ip = - try: - route.source.address() - except ValueError as e: - # This should not occur really. - error "Address conversion error", exception = e.name, msg = e.msg - return err("Invalid IP address") - ok(ip) diff --git a/tests/codex/helpers/nodeutils.nim b/tests/codex/helpers/nodeutils.nim index 55ab984b..857092bb 100644 --- a/tests/codex/helpers/nodeutils.nim +++ b/tests/codex/helpers/nodeutils.nim @@ -219,11 +219,8 @@ proc generateNodes*( if config.enableBootstrap: waitFor switch.peerInfo.update() - let (announceAddrs, discoveryAddrs) = nattedAddress( - NatConfig(hasExtIp: false, nat: NatNone), - switch.peerInfo.addrs, - bindPort.Port, - ) + let (announceAddrs, discoveryAddrs) = + nattedAddress(switch.peerInfo.addrs, bindPort.Port) blockDiscovery.updateAnnounceRecord(announceAddrs) blockDiscovery.updateDhtRecord(discoveryAddrs) if blockDiscovery.dhtRecord.isSome: diff --git a/vendor/lrucache.nim b/vendor/lrucache.nim index ba577369..8767ade0 160000 --- a/vendor/lrucache.nim +++ b/vendor/lrucache.nim @@ -1 +1 @@ -Subproject commit ba57736921b2972163b673fc706e7659e7c5cbd6 +Subproject commit 8767ade0b76ea5b5d4ce24a52d0c58a6ebeb66cd diff --git a/vendor/nim-bearssl b/vendor/nim-bearssl index f08d7220..667b4044 160000 --- a/vendor/nim-bearssl +++ b/vendor/nim-bearssl @@ -1 +1 @@ -Subproject commit f08d72203f9e110c099c6f393e1c0640fcbe176f +Subproject commit 667b40440a53a58e9f922e29e20818720c62d9ac diff --git a/vendor/nim-blscurve b/vendor/nim-blscurve index f4d0de2e..de2d3c79 160000 --- a/vendor/nim-blscurve +++ b/vendor/nim-blscurve @@ -1 +1 @@ -Subproject commit f4d0de2eece20380541fbf73d4b8bf57dc214b3b +Subproject commit de2d3c79264bba18dbea469c8c5c4b3bb3c8bc55 diff --git a/vendor/nim-chronos b/vendor/nim-chronos index 0646c444..c04576d8 160000 --- a/vendor/nim-chronos +++ b/vendor/nim-chronos @@ -1 +1 @@ -Subproject commit 0646c444fce7c7ed08ef6f2c9a7abfd172ffe655 +Subproject commit c04576d829b8a0a1b12baaa8bc92037501b3a4a0 diff --git a/vendor/nim-contract-abi b/vendor/nim-contract-abi index 0a7b4cec..842f4891 160000 --- a/vendor/nim-contract-abi +++ b/vendor/nim-contract-abi @@ -1 +1 @@ -Subproject commit 0a7b4cecce725bcb11ad8648035a92704a8854d3 +Subproject commit 842f48910be4f388bcbf8abf1f02aba1d5e2ee64 diff --git a/vendor/nim-eth b/vendor/nim-eth index d9135e6c..dcfbc429 160000 --- a/vendor/nim-eth +++ b/vendor/nim-eth @@ -1 +1 @@ -Subproject commit d9135e6c3c5d6d819afdfb566aa8d958756b73a8 +Subproject commit dcfbc4291d39b59563828c3e32be4d51a2f25931 diff --git a/vendor/nim-ethers b/vendor/nim-ethers index 965b8cd7..30871c7b 160000 --- a/vendor/nim-ethers +++ b/vendor/nim-ethers @@ -1 +1 @@ -Subproject commit 965b8cd752544df96b5effecbbd27a8f56a25d62 +Subproject commit 30871c7b1d5784e36c51223bd36ef6f1fffcc030 diff --git a/vendor/nim-faststreams b/vendor/nim-faststreams index ce27581a..cf8d4d22 160000 --- a/vendor/nim-faststreams +++ b/vendor/nim-faststreams @@ -1 +1 @@ -Subproject commit ce27581a3e881f782f482cb66dc5b07a02bd615e +Subproject commit cf8d4d22636b8e514caf17e49f9c786ac56b0e85 diff --git a/vendor/nim-http-utils b/vendor/nim-http-utils index c53852d9..8bb1acba 160000 --- a/vendor/nim-http-utils +++ b/vendor/nim-http-utils @@ -1 +1 @@ -Subproject commit c53852d9e24205b6363bba517fa8ee7bde823691 +Subproject commit 8bb1acbaa4b86eb866145b0d468eff64a57d1897 diff --git a/vendor/nim-json-rpc b/vendor/nim-json-rpc index b6e40a77..cbe8edf6 160000 --- a/vendor/nim-json-rpc +++ b/vendor/nim-json-rpc @@ -1 +1 @@ -Subproject commit b6e40a776fa2d00b97a9366761fb7da18f31ae5c +Subproject commit cbe8edf69d743a787b76b1cd25bfc4eae89927f7 diff --git a/vendor/nim-json-serialization b/vendor/nim-json-serialization index a6dcf03e..6eadb6e9 160000 --- a/vendor/nim-json-serialization +++ b/vendor/nim-json-serialization @@ -1 +1 @@ -Subproject commit a6dcf03e04e179127a5fcb7e495d19a821d56c17 +Subproject commit 6eadb6e939ffa7882ff5437033c11a9464d3385c diff --git a/vendor/nim-leveldbstatic b/vendor/nim-leveldbstatic index 5a0cd8de..378ef63e 160000 --- a/vendor/nim-leveldbstatic +++ b/vendor/nim-leveldbstatic @@ -1 +1 @@ -Subproject commit 5a0cd8de6b2363827c43cafd3ed346ecee427e1e +Subproject commit 378ef63e261e3b5834a3567404edc3ce838498b3 diff --git a/vendor/nim-libbacktrace b/vendor/nim-libbacktrace index 99bc2ba1..6da0cda8 160000 --- a/vendor/nim-libbacktrace +++ b/vendor/nim-libbacktrace @@ -1 +1 @@ -Subproject commit 99bc2ba16bc2d44f9a97e706304f64744d913d7f +Subproject commit 6da0cda88ab7780bd5fd342327adb91ab84692aa diff --git a/vendor/nim-libp2p b/vendor/nim-libp2p index e82080f7..7eaf79fe 160000 --- a/vendor/nim-libp2p +++ b/vendor/nim-libp2p @@ -1 +1 @@ -Subproject commit e82080f7b1aa61c6d35fa5311b873f41eff4bb52 +Subproject commit 7eaf79fefe45b03a4281e3505d5fcc97b32df39c diff --git a/vendor/nim-metrics b/vendor/nim-metrics index 9b9afee9..cacfdc12 160000 --- a/vendor/nim-metrics +++ b/vendor/nim-metrics @@ -1 +1 @@ -Subproject commit 9b9afee96357ad82dabf4563cf292f89b50423df +Subproject commit cacfdc12454a0804c65112b9f4f50d1375208dcd diff --git a/vendor/nim-nat-traversal b/vendor/nim-nat-traversal index 860e18c3..6508ce75 160000 --- a/vendor/nim-nat-traversal +++ b/vendor/nim-nat-traversal @@ -1 +1 @@ -Subproject commit 860e18c37667b5dd005b94c63264560c35d88004 +Subproject commit 6508ce75060878dfcdfa21f94721672c69a1823b diff --git a/vendor/nim-ngtcp2 b/vendor/nim-ngtcp2 index 791eb859..6834f475 160000 --- a/vendor/nim-ngtcp2 +++ b/vendor/nim-ngtcp2 @@ -1 +1 @@ -Subproject commit 791eb859145f9f268eb23eb9cbe777bdd7699c4d +Subproject commit 6834f4756b6af58356ac9c4fef3d71db3c3ae5fe diff --git a/vendor/nim-nitro b/vendor/nim-nitro index 5ccdeb46..e3719433 160000 --- a/vendor/nim-nitro +++ b/vendor/nim-nitro @@ -1 +1 @@ -Subproject commit 5ccdeb46e06dcf5cef80d0acbb80ee8a17d596e7 +Subproject commit e3719433d5ace25947c468787c805969642b3913 diff --git a/vendor/nim-presto b/vendor/nim-presto index 62225bfa..92b1c7ff 160000 --- a/vendor/nim-presto +++ b/vendor/nim-presto @@ -1 +1 @@ -Subproject commit 62225bfa7ce703a99e04680bfc3498e69b52897f +Subproject commit 92b1c7ff141e6920e1f8a98a14c35c1fa098e3be diff --git a/vendor/nim-protobuf-serialization b/vendor/nim-protobuf-serialization index 4d74e157..5a31137a 160000 --- a/vendor/nim-protobuf-serialization +++ b/vendor/nim-protobuf-serialization @@ -1 +1 @@ -Subproject commit 4d74e157cdf1bdcd0ffd41519ebde740c4b80447 +Subproject commit 5a31137a82c2b6a989c9ed979bb636c7a49f570e diff --git a/vendor/nim-quic b/vendor/nim-quic index 525842ae..ddcb31ff 160000 --- a/vendor/nim-quic +++ b/vendor/nim-quic @@ -1 +1 @@ -Subproject commit 525842aeca6111fd5035568d0f59aa2b338cc29d +Subproject commit ddcb31ffb74b5460ab37fd13547eca90594248bc diff --git a/vendor/nim-serde b/vendor/nim-serde index 649ae60e..5ced7c88 160000 --- a/vendor/nim-serde +++ b/vendor/nim-serde @@ -1 +1 @@ -Subproject commit 649ae60e05ec432738d41eb8d613c5d7f434c4a3 +Subproject commit 5ced7c88b97d99c582285ce796957fb71fd42434 diff --git a/vendor/nim-serialization b/vendor/nim-serialization index b0f2fa32..2086c996 160000 --- a/vendor/nim-serialization +++ b/vendor/nim-serialization @@ -1 +1 @@ -Subproject commit b0f2fa32960ea532a184394b0f27be37bd80248b +Subproject commit 2086c99608b4bf472e1ef5fe063710f280243396 diff --git a/vendor/nim-sqlite3-abi b/vendor/nim-sqlite3-abi index 6797c318..05bbff1a 160000 --- a/vendor/nim-sqlite3-abi +++ b/vendor/nim-sqlite3-abi @@ -1 +1 @@ -Subproject commit 6797c31836bff377bf50f1ac7bf8122449bf99ba +Subproject commit 05bbff1af4e8fe2d972ba4b0667b89ca94d3ebba diff --git a/vendor/nim-stew b/vendor/nim-stew index b6616873..a6e19813 160000 --- a/vendor/nim-stew +++ b/vendor/nim-stew @@ -1 +1 @@ -Subproject commit b66168735d6f3841c5239c3169d3fe5fe98b1257 +Subproject commit a6e198132097fb544d04959aeb3b839e1408f942 diff --git a/vendor/nim-taskpools b/vendor/nim-taskpools index 97f76fae..66585e2e 160000 --- a/vendor/nim-taskpools +++ b/vendor/nim-taskpools @@ -1 +1 @@ -Subproject commit 97f76faef6ba64bc77d9808c27ec5e9917e7cfde +Subproject commit 66585e2e960b7695e48ea60377fb3aeac96406e8 diff --git a/vendor/nim-testutils b/vendor/nim-testutils index e4d37dc1..4d37244f 160000 --- a/vendor/nim-testutils +++ b/vendor/nim-testutils @@ -1 +1 @@ -Subproject commit e4d37dc1652d5c63afb89907efb5a5e812261797 +Subproject commit 4d37244f9f5e1acd8592a4ceb5c3fc47bc160181 diff --git a/vendor/nim-toml-serialization b/vendor/nim-toml-serialization index b5b387e6..fea85b27 160000 --- a/vendor/nim-toml-serialization +++ b/vendor/nim-toml-serialization @@ -1 +1 @@ -Subproject commit b5b387e6fb2a7cc75d54a269b07cc6218361bd46 +Subproject commit fea85b27f0badcf617033ca1bc05444b5fd8aa7a diff --git a/vendor/nim-websock b/vendor/nim-websock index 35ae76f1..ebe308a7 160000 --- a/vendor/nim-websock +++ b/vendor/nim-websock @@ -1 +1 @@ -Subproject commit 35ae76f1559e835c80f9c1a3943bf995d3dd9eb5 +Subproject commit ebe308a79a7b440a11dfbe74f352be86a3883508 diff --git a/vendor/nim-zlib b/vendor/nim-zlib index c71efff5..91cf360b 160000 --- a/vendor/nim-zlib +++ b/vendor/nim-zlib @@ -1 +1 @@ -Subproject commit c71efff5fd1721362b3363dc7d0e2a4c0dbc6453 +Subproject commit 91cf360b1aeb2e0c753ff8bac6de22a41c5ed8cd diff --git a/vendor/stint b/vendor/stint index 470b7892..5c5e01ce 160000 --- a/vendor/stint +++ b/vendor/stint @@ -1 +1 @@ -Subproject commit 470b7892561b5179ab20bd389a69217d6213fe58 +Subproject commit 5c5e01cef089a261474b7abfe246b37447aaa8ed