Better support for ivp6 and more tests

This commit is contained in:
Arnaud 2026-04-10 18:27:21 +04:00
parent 24c1515778
commit 8110abcb9d
No known key found for this signature in database
GPG Key ID: A6C7C781817146FA
4 changed files with 151 additions and 48 deletions

View File

@ -137,7 +137,12 @@ proc getRoutePrefSrc(bindIp: IpAddress): (Option[IpAddress], PrefSrcStatus) =
let bindAddress = initTAddress(bindIp, Port(0))
if bindAddress.isAnyLocal():
let ip = getRouteIpv4()
let ip =
if bindIp.family == IpAddressFamily.IPv6:
getRouteIpv6()
else:
getRouteIpv4()
if ip.isErr():
# No route was found, log error and continue without IP.
error "No routable IP address found, check your network connection",

View File

@ -1,6 +1,6 @@
{.push raises: [].}
import std/[net, tables, hashes], pkg/results, chronos, chronicles
import std/[net, tables, hashes, options], pkg/results, chronos, chronicles
import pkg/libp2p
@ -10,32 +10,6 @@ type NatStrategy* = enum
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
@ -43,18 +17,11 @@ 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)
proc getRoute(publicAddress: TransportAddress): Result[IpAddress, cstring] =
let route = getBestRoute(publicAddress)
if route.source.isUnspecified():
err("No best ipv4 route found")
err("No best route found")
else:
let ip =
try:
@ -64,3 +31,42 @@ proc getRouteIpv4*(): Result[IpAddress, cstring] =
error "Address conversion error", exception = e.name, msg = e.msg
return err("Invalid IP address")
ok(ip)
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)
)
return getRoute(publicAddress)
proc getRouteIpv6*(): Result[IpAddress, cstring] =
# Note: `googleDnsIpv6` is only used as an "example" IP to find the best route,
# no data is sent over the network to this IP!
const googleDnsIpv6 = TransportAddress(
family: AddressFamily.IPv6,
# 2001:4860:4860::8888
address_v6: [32'u8, 1, 72, 96, 72, 96, 0, 0, 0, 0, 0, 0, 0, 0, 136, 136],
port: Port(0),
)
return getRoute(googleDnsIpv6)
# If bindIp is a anyLocal address (0.0.0.0 or ::),
# the function will find the best ip address.
# Otherwise, it will just return the ip as it is.
proc getBestLocalAddress*(bindIp: IpAddress): Option[IpAddress] =
let bindAddress = initTAddress(bindIp, Port(0))
if bindAddress.isAnyLocal():
let ip =
if bindIp.family == IpAddressFamily.IPv6:
getRouteIpv6()
else:
getRouteIpv4()
if ip.isOk():
return some(ip.get())
return none(IpAddress)
else:
return some(bindIp)

View File

@ -21,16 +21,18 @@ suite "NAT Address Tests":
# Expected results
let
expectedDiscoveryAddrs = @[
MultiAddress.init("/ip4/8.8.8.8/udp/1234").expect("valid multiaddr"),
MultiAddress.init("/ip4/8.8.8.8/udp/1234").expect("valid multiaddr"),
MultiAddress.init("/ip4/8.8.8.8/udp/1234").expect("valid multiaddr"),
]
expectedlibp2pAddrs = @[
MultiAddress.init("/ip4/8.8.8.8/tcp/5000").expect("valid multiaddr"),
MultiAddress.init("/ip4/8.8.8.8/tcp/5000").expect("valid multiaddr"),
MultiAddress.init("/ip4/8.8.8.8/tcp/5000").expect("valid multiaddr"),
]
expectedDiscoveryAddrs =
@[
MultiAddress.init("/ip4/8.8.8.8/udp/1234").expect("valid multiaddr"),
MultiAddress.init("/ip4/8.8.8.8/udp/1234").expect("valid multiaddr"),
MultiAddress.init("/ip4/8.8.8.8/udp/1234").expect("valid multiaddr"),
]
expectedlibp2pAddrs =
@[
MultiAddress.init("/ip4/8.8.8.8/tcp/5000").expect("valid multiaddr"),
MultiAddress.init("/ip4/8.8.8.8/tcp/5000").expect("valid multiaddr"),
MultiAddress.init("/ip4/8.8.8.8/tcp/5000").expect("valid multiaddr"),
]
#ipv6Addr = MultiAddress.init("/ip6/::1/tcp/5000").expect("valid multiaddr")
addrs = @[localAddr, anyAddr, publicAddr]
@ -41,3 +43,26 @@ suite "NAT Address Tests":
# Verify results
check(discoveryAddrs == expectedDiscoveryAddrs)
check(libp2pAddrs == expectedlibp2pAddrs)
suite "setupAddress":
test "public bind IP with NatNone returns bind IP":
let
bindIp = parseIpAddress("8.8.8.8")
natConfig = NatConfig(hasExtIp: false, nat: NatStrategy.NatNone)
(ip, tcpPort, udpPort) =
setupAddress(natConfig, bindIp, Port(5000), Port(5001), "test")
check ip == some(bindIp)
check tcpPort == some(Port(5000))
check udpPort == some(Port(5001))
test "private bind IP with NatNone returns no IP":
let
bindIp = parseIpAddress("192.168.1.1")
natConfig = NatConfig(hasExtIp: false, nat: NatStrategy.NatNone)
(ip, tcpPort, udpPort) =
setupAddress(natConfig, bindIp, Port(5000), Port(5001), "test")
check ip == none(IpAddress)
check tcpPort == some(Port(5000))
check udpPort == some(Port(5001))

View File

@ -0,0 +1,67 @@
import std/[unittest, net, options]
import pkg/chronos
import ../../storage/utils/natutils
suite "isGlobalUnicast":
test "localhost IPv4 is not global unicast":
check not isGlobalUnicast(parseIpAddress("127.0.0.1"))
test "unspecified IPv4 is not global unicast":
check not isGlobalUnicast(parseIpAddress("0.0.0.0"))
test "link-local IPv4 is not global unicast":
check not isGlobalUnicast(parseIpAddress("169.254.1.1"))
test "private IPv4 is not global unicast":
check not isGlobalUnicast(parseIpAddress("10.0.0.1"))
test "public IPv4 is global unicast":
check isGlobalUnicast(parseIpAddress("8.8.8.8"))
test "localhost IPv6 is not global unicast":
check not isGlobalUnicast(parseIpAddress("::1"))
test "unspecified IPv6 is not global unicast":
check not isGlobalUnicast(parseIpAddress("::"))
test "link-local IPv6 is not global unicast":
check not isGlobalUnicast(parseIpAddress("fe80::1"))
test "private IPv6 is not global unicast":
check not isGlobalUnicast(parseIpAddress("fc00::1"))
test "public IPv6 is global unicast":
check isGlobalUnicast(parseIpAddress("2606:4700::1"))
suite "getRoute":
test "getRouteIpv4 returns a valid IPv4":
let res = getRouteIpv4()
check res.isOk
check res.get().family == IpAddressFamily.IPv4
test "getRouteIpv6 returns a valid IPv6":
let res = getRouteIpv6()
# If the machine does not have a global route because
# it is not configured for IPv6, the test will fail
# because it didn't find the best route. In that case,
# we can just skip the test, because it is not a problem
# with the test itself but the machine configuration.
if res.isErr:
check res.error == "No best route found"
else:
check res.get().family == IpAddressFamily.IPv6
suite "getBestLocalAddress":
test "specific IPv4 is returned as it is":
let ip = parseIpAddress("192.168.1.1")
check getBestLocalAddress(ip) == some(ip)
test "specific IPv6 is returned as it is":
let ip = parseIpAddress("2606:4700::1")
check getBestLocalAddress(ip) == some(ip)
test "0.0.0.0 resolves to a local IPv4":
let res = getBestLocalAddress(parseIpAddress("0.0.0.0"))
check res.isSome
check res.get().family == IpAddressFamily.IPv4