Skip to content

Commit

Permalink
network: support for uPnP and PMP nat traversal (#1050)
Browse files Browse the repository at this point in the history
* add support for uPnP and PMP nat traversal

* update cli flags

* merge with master

* use listener address from switch

* update build script to rename version file

* fix styling issues

* update docker files

- remove Disc_ip env
- update CODE_NAT parsing logic

* code cleanup

* move nat flag parsing logic to conf.nim
  • Loading branch information
munna0908 authored Jan 9, 2025
1 parent 407f778 commit 74c46b3
Show file tree
Hide file tree
Showing 19 changed files with 689 additions and 79 deletions.
25 changes: 23 additions & 2 deletions build.nims
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,28 @@ mode = ScriptMode.Verbose

import std/os except commandLineParams

const VendorPath = "vendor/nim-nat-traversal/vendor/libnatpmp-upstream"
let
oldVersionFile = joinPath(VendorPath, "VERSION")
newVersionFile = joinPath(VendorPath, "VERSION_temp")

proc renameFile(oldName, newName: string) =
if fileExists(oldName):
mvFile(oldName, newName)
else:
echo "File ", oldName, " does not exist"


### Helper functions
proc buildBinary(name: string, srcDir = "./", params = "", lang = "c") =
# This is a quick workaround to avoid VERSION file conflict on macOS
# More details here: https://github.com/codex-storage/nim-codex/issues/1059
if defined(macosx):
renameFile(oldVersionFile, newVersionFile)

if not dirExists "build":
mkDir "build"

# allow something like "nim nimbus --verbosity:0 --hints:off nimbus.nims"
var extra_params = params
when compiles(commandLineParams):
Expand All @@ -19,8 +37,11 @@ proc buildBinary(name: string, srcDir = "./", params = "", lang = "c") =
# Place build output in 'build' folder, even if name includes a longer path.
outName = os.lastPathPart(name)
cmd = "nim " & lang & " --out:build/" & outName & " " & extra_params & " " & srcDir & name & ".nim"

exec(cmd)
try:
exec(cmd)
finally:
if defined(macosx):
renameFile(newVersionFile, oldVersionFile)

proc test(name: string, srcDir = "tests/", params = "", lang = "c") =
buildBinary name, srcDir, params
Expand Down
7 changes: 0 additions & 7 deletions codex.nim
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,6 @@ when isMainModule:
config.setupLogging()
config.setupMetrics()

if config.nat == ValidIpAddress.init(IPv4_any()):
error "`--nat` cannot be set to the any (`0.0.0.0`) address"
quit QuitFailure

if config.nat == ValidIpAddress.init("127.0.0.1"):
warn "`--nat` is set to loopback, your node wont properly announce over the DHT"

if not(checkAndCreateDataDir((config.dataDir).string)):
# We are unable to access/create data folder or data folder's
# permissions are insecure.
Expand Down
29 changes: 6 additions & 23 deletions codex/codex.nim
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import ./utils/addrutils
import ./namespaces
import ./codextypes
import ./logutils
import ./nat

logScope:
topics = "codex node"
Expand Down Expand Up @@ -158,30 +159,13 @@ proc start*(s: CodexServer) {.async.} =

await s.codexNode.switch.start()

let
# TODO: Can't define these as constants, pity
natIpPart = MultiAddress.init("/ip4/" & $s.config.nat & "/")
.expect("Should create multiaddress")
anyAddrIp = MultiAddress.init("/ip4/0.0.0.0/")
.expect("Should create multiaddress")
loopBackAddrIp = MultiAddress.init("/ip4/127.0.0.1/")
.expect("Should create multiaddress")

# announce addresses should be set to bound addresses,
# but the IP should be mapped to the provided nat ip
announceAddrs = s.codexNode.switch.peerInfo.addrs.mapIt:
block:
let
listenIPPart = it[multiCodec("ip4")].expect("Should get IP")

if listenIPPart == anyAddrIp or
(listenIPPart == loopBackAddrIp and natIpPart != loopBackAddrIp):
it.remapAddr(s.config.nat.some)
else:
it
let (announceAddrs,discoveryAddrs)= nattedAddress(
s.config.nat,
s.codexNode.switch.peerInfo.addrs,
s.config.discoveryPort)

s.codexNode.discovery.updateAnnounceRecord(announceAddrs)
s.codexNode.discovery.updateDhtRecord(s.config.nat, s.config.discoveryPort)
s.codexNode.discovery.updateDhtRecord(discoveryAddrs)

await s.bootstrapInteractions()
await s.codexNode.start()
Expand Down Expand Up @@ -243,7 +227,6 @@ proc new*(
discovery = Discovery.new(
switch.peerInfo.privateKey,
announceAddrs = config.listenAddrs,
bindIp = config.discoveryIp,
bindPort = config.discoveryPort,
bootstrapNodes = config.bootstrapNodes,
store = discoveryStore)
Expand Down
53 changes: 39 additions & 14 deletions codex/conf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ import ./logutils
import ./stores
import ./units
import ./utils
import ./nat
import ./utils/natutils
from ./validationconfig import MaxSlots, ValidationGroups

export units, net, codextypes, logutils
export units, net, codextypes, logutils, completeCmdArg, parseCmdArg, NatConfig
export ValidationGroups, MaxSlots

export
Expand Down Expand Up @@ -142,20 +144,12 @@ type
abbr: "i"
name: "listen-addrs" }: seq[MultiAddress]

# TODO: change this once we integrate nat support
nat* {.
desc: "IP Addresses to announce behind a NAT"
defaultValue: ValidIpAddress.init("127.0.0.1")
defaultValueDesc: "127.0.0.1"
abbr: "a"
name: "nat" }: ValidIpAddress

discoveryIp* {.
desc: "Discovery listen address"
defaultValue: ValidIpAddress.init(IPv4_any())
defaultValueDesc: "0.0.0.0"
abbr: "e"
name: "disc-ip" }: ValidIpAddress
desc: "Specify method to use for determining public address. " &
"Must be one of: any, none, upnp, pmp, extip:<IP>"
defaultValue: NatConfig(hasExtIp: false, nat: NatAny)
defaultValueDesc: "any"
name: "nat" }: NatConfig

discoveryPort* {.
desc: "Discovery (UDP) port"
Expand Down Expand Up @@ -469,6 +463,31 @@ proc parseCmdArg*(T: type SignedPeerRecord, uri: string): T =
quit QuitFailure
res

func parseCmdArg*(T: type NatConfig, p: string): T {.raises: [ValueError].} =
case p.toLowerAscii:
of "any":
NatConfig(hasExtIp: false, nat: NatStrategy.NatAny)
of "none":
NatConfig(hasExtIp: false, nat: NatStrategy.NatNone)
of "upnp":
NatConfig(hasExtIp: false, nat: NatStrategy.NatUpnp)
of "pmp":
NatConfig(hasExtIp: false, nat: NatStrategy.NatPmp)
else:
if p.startsWith("extip:"):
try:
let ip = ValidIpAddress.init(p[6..^1])
NatConfig(hasExtIp: true, extIp: ip)
except ValueError:
let error = "Not a valid IP address: " & p[6..^1]
raise newException(ValueError, error)
else:
let error = "Not a valid NAT option: " & p
raise newException(ValueError, error)

proc completeCmdArg*(T: type NatConfig; val: string): seq[string] =
return @[]

proc parseCmdArg*(T: type EthAddress, address: string): T =
EthAddress.init($address).get()

Expand Down Expand Up @@ -531,6 +550,12 @@ proc readValue*(r: var TomlReader, val: var Duration)
quit QuitFailure
val = dur

proc readValue*(r: var TomlReader, val: var NatConfig)
{.raises: [SerializationError].} =
val = try: parseCmdArg(NatConfig, r.readValue(string))
except CatchableError as err:
raise newException(SerializationError, err.msg)

# no idea why confutils needs this:
proc completeCmdArg*(T: type EthAddress; val: string): seq[string] =
discard
Expand Down
11 changes: 4 additions & 7 deletions codex/discovery.nim
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,14 @@ proc updateAnnounceRecord*(d: Discovery, addrs: openArray[MultiAddress]) =
d.protocol.updateRecord(d.providerRecord)
.expect("Should update SPR")

proc updateDhtRecord*(d: Discovery, ip: ValidIpAddress, port: Port) =
proc updateDhtRecord*(d: Discovery, addrs: openArray[MultiAddress]) =
## Update providers record
##

trace "Updating Dht record", ip, port = $port
trace "Updating Dht record", addrs = addrs
d.dhtRecord = SignedPeerRecord.init(
d.key, PeerRecord.init(d.peerId, @[
MultiAddress.init(
ip,
IpTransportProtocol.udpProtocol,
port)])).expect("Should construct signed record").some
d.key, PeerRecord.init(d.peerId, @addrs))
.expect("Should construct signed record").some

if not d.protocol.isNil:
d.protocol.updateRecord(d.dhtRecord)
Expand Down
Loading

0 comments on commit 74c46b3

Please sign in to comment.