diff --git a/chronos.nimble b/chronos.nimble index 1bec9006..3427fa49 100644 --- a/chronos.nimble +++ b/chronos.nimble @@ -1,5 +1,5 @@ packageName = "chronos" -version = "2.2.4" +version = "2.2.5" author = "Status Research & Development GmbH" description = "Chronos" license = "Apache License 2.0 or MIT" diff --git a/chronos/asyncloop.nim b/chronos/asyncloop.nim index 59947b15..c49c612b 100644 --- a/chronos/asyncloop.nim +++ b/chronos/asyncloop.nim @@ -721,14 +721,14 @@ proc removeTimer*(at: uint64, cb: CallbackFunc, udata: pointer = nil) {. inline, deprecated: "Use removeTimer(Duration, cb, udata)".} = removeTimer(Moment.init(int64(at), Millisecond), cb, udata) -proc sleepAsync*(ms: Duration): Future[void] = +proc sleepAsync*(duration: Duration): Future[void] = ## Suspends the execution of the current async procedure for the next - ## ``ms`` milliseconds. + ## ``duration`` time. var retFuture = newFuture[void]("sleepAsync") proc completion(data: pointer) = if not retFuture.finished: retFuture.complete() - addTimer(Moment.fromNow(ms), completion, cast[pointer](retFuture)) + addTimer(Moment.fromNow(duration), completion, cast[pointer](retFuture)) return retFuture proc sleepAsync*(ms: int): Future[void] {. diff --git a/chronos/transport.nim b/chronos/transport.nim index 5602a8aa..f0071788 100644 --- a/chronos/transport.nim +++ b/chronos/transport.nim @@ -7,5 +7,5 @@ # Apache License, version 2.0, (LICENSE-APACHEv2) # MIT license (LICENSE-MIT) -import transports/[datagram, stream, common] -export datagram, common, stream +import transports/[datagram, stream, common, ipnet, osnet] +export datagram, common, stream, ipnet, osnet diff --git a/chronos/transports/datagram.nim b/chronos/transports/datagram.nim index 641ca8b3..18f073df 100644 --- a/chronos/transports/datagram.nim +++ b/chronos/transports/datagram.nim @@ -15,6 +15,10 @@ when defined(windows): import winlean else: import posix + var IP_MULTICAST_TTL* {.importc: "IP_MULTICAST_TTL", + header: "".}: cint + var IPV6_MULTICAST_HOPS* {.importc: "IPV6_MULTICAST_HOPS", + header: "".}: cint type VectorKind = enum @@ -105,6 +109,8 @@ when defined(windows): const IOC_VENDOR = DWORD(0x18000000) SIO_UDP_CONNRESET = DWORD(winlean.IOC_IN) or IOC_VENDOR or DWORD(12) + IPPROTO_IP = DWORD(0) + IP_TTL = DWORD(4) proc writeDatagramLoop(udata: pointer) = var bytesCount: int32 @@ -250,7 +256,8 @@ when defined(windows): flags: set[ServerFlags], udata: pointer, child: DatagramTransport, - bufferSize: int): DatagramTransport = + bufferSize: int, + ttl: int): DatagramTransport = var localSock: AsyncFD doAssert(remote.family == local.family) doAssert(not isNil(cbproc)) @@ -287,6 +294,13 @@ when defined(windows): closeSocket(localSock) raiseTransportOsError(err) + if ttl > 0: + if not setSockOpt(localSock, IPPROTO_IP, IP_TTL, DWORD(ttl)): + let err = osLastError() + if sock == asyncInvalidSocket: + closeSocket(localSock) + raiseTransportOsError(err) + ## Fix for Q263823. var bytesRet: DWORD var bval = WINBOOL(0) @@ -437,7 +451,8 @@ else: flags: set[ServerFlags], udata: pointer, child: DatagramTransport = nil, - bufferSize: int): DatagramTransport = + bufferSize: int, + ttl: int): DatagramTransport = var localSock: AsyncFD doAssert(remote.family == local.family) doAssert(not isNil(cbproc)) @@ -478,6 +493,20 @@ else: closeSocket(localSock) raiseTransportOsError(err) + if ttl > 0: + var res: bool + if local.family == AddressFamily.IPv4: + res = setSockOpt(localSock, posix.IPPROTO_IP, IP_MULTICAST_TTL, + cint(ttl)) + elif local.family == AddressFamily.IPv6: + res = setSockOpt(localSock, posix.IPPROTO_IP, IPV6_MULTICAST_HOPS, + cint(ttl)) + if not res: + let err = osLastError() + if sock == asyncInvalidSocket: + closeSocket(localSock) + raiseTransportOsError(err) + if local.port != Port(0): var saddr: Sockaddr_storage var slen: SockLen @@ -550,7 +579,8 @@ proc newDatagramTransport*(cbproc: DatagramCallback, flags: set[ServerFlags] = {}, udata: pointer = nil, child: DatagramTransport = nil, - bufSize: int = DefaultDatagramBufferSize + bufSize: int = DefaultDatagramBufferSize, + ttl: int = 0 ): DatagramTransport = ## Create new UDP datagram transport (IPv4). ## @@ -561,9 +591,11 @@ proc newDatagramTransport*(cbproc: DatagramCallback, ## ``sock`` - application-driven socket to use. ## ``flags`` - flags that will be applied to socket. ## ``udata`` - custom argument which will be passed to ``cbproc``. - ## ``bufSize`` - size of internal buffer + ## ``bufSize`` - size of internal buffer. + ## ``ttl`` - TTL for UDP datagram packet (only usable when flags has + ## ``Broadcast`` option). result = newDatagramTransportCommon(cbproc, remote, local, sock, - flags, udata, child, bufSize) + flags, udata, child, bufSize, ttl) proc newDatagramTransport*[T](cbproc: DatagramCallback, udata: ref T, @@ -572,13 +604,14 @@ proc newDatagramTransport*[T](cbproc: DatagramCallback, sock: AsyncFD = asyncInvalidSocket, flags: set[ServerFlags] = {}, child: DatagramTransport = nil, - bufSize: int = DefaultDatagramBufferSize + bufSize: int = DefaultDatagramBufferSize, + ttl: int = 0 ): DatagramTransport = var fflags = flags + {GCUserData} GC_ref(udata) result = newDatagramTransportCommon(cbproc, remote, local, sock, fflags, cast[pointer](udata), - child, bufSize) + child, bufSize, ttl) proc newDatagramTransport6*(cbproc: DatagramCallback, remote: TransportAddress = AnyAddress6, @@ -587,7 +620,8 @@ proc newDatagramTransport6*(cbproc: DatagramCallback, flags: set[ServerFlags] = {}, udata: pointer = nil, child: DatagramTransport = nil, - bufSize: int = DefaultDatagramBufferSize + bufSize: int = DefaultDatagramBufferSize, + ttl: int = 0 ): DatagramTransport = ## Create new UDP datagram transport (IPv6). ## @@ -599,8 +633,10 @@ proc newDatagramTransport6*(cbproc: DatagramCallback, ## ``flags`` - flags that will be applied to socket. ## ``udata`` - custom argument which will be passed to ``cbproc``. ## ``bufSize`` - size of internal buffer. + ## ``ttl`` - TTL for UDP datagram packet (only usable when flags has + ## ``Broadcast`` option). result = newDatagramTransportCommon(cbproc, remote, local, sock, - flags, udata, child, bufSize) + flags, udata, child, bufSize, ttl) proc newDatagramTransport6*[T](cbproc: DatagramCallback, udata: ref T, @@ -609,13 +645,14 @@ proc newDatagramTransport6*[T](cbproc: DatagramCallback, sock: AsyncFD = asyncInvalidSocket, flags: set[ServerFlags] = {}, child: DatagramTransport = nil, - bufSize: int = DefaultDatagramBufferSize + bufSize: int = DefaultDatagramBufferSize, + ttl: int = 0 ): DatagramTransport = var fflags = flags + {GCUserData} GC_ref(udata) result = newDatagramTransportCommon(cbproc, remote, local, sock, fflags, cast[pointer](udata), - child, bufSize) + child, bufSize, ttl) proc join*(transp: DatagramTransport): Future[void] = ## Wait until the transport ``transp`` will be closed. diff --git a/chronos/transports/ipnet.nim b/chronos/transports/ipnet.nim new file mode 100644 index 00000000..7aceaffc --- /dev/null +++ b/chronos/transports/ipnet.nim @@ -0,0 +1,712 @@ +# +# Chronos IP Network +# (c) Copyright 2018-Present +# Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) + +## This module implements various IP network utility procedures. +import endians, strutils +import common +export common + +type + IpMask* = object + case family*: AddressFamily + of AddressFamily.None, AddressFamily.Unix: + discard + of AddressFamily.IPv4: + mask4*: uint32 + of AddressFamily.IPv6: + mask6*: array[2, uint64] + + IpNet* = object + host*: TransportAddress + mask*: IpMask + +proc toNetworkOrder(mask: IpMask): IpMask {.inline.} = + ## Converts ``mask`` from host order (which can be big/little-endian) to + ## network order (which is big-endian) representation. + result = IpMask(family: mask.family) + if mask.family == AddressFamily.IPv4: + bigEndian32(cast[pointer](addr result.mask4), + cast[pointer](unsafeAddr mask.mask4)) + elif mask.family == AddressFamily.IPv6: + bigEndian64(cast[pointer](addr result.mask6[0]), + cast[pointer](unsafeAddr mask.mask6[0])) + bigEndian64(cast[pointer](addr result.mask6[1]), + cast[pointer](unsafeAddr mask.mask6[1])) + +proc toHostOrder(mask: IpMask): IpMask {.inline.} = + ## Converts ``mask`` from network order (which is big-endian) back to + ## host representation (which can be big/little-endian). + when system.cpuEndian == bigEndian: + result = mask + else: + result = IpMask(family: mask.family) + if mask.family == AddressFamily.IPv4: + swapEndian32(cast[pointer](addr result.mask4), + cast[pointer](unsafeAddr mask.mask4)) + elif mask.family == AddressFamily.IPv6: + swapEndian64(cast[pointer](addr result.mask6[0]), + cast[pointer](unsafeAddr mask.mask6[0])) + swapEndian64(cast[pointer](addr result.mask6[1]), + cast[pointer](unsafeAddr mask.mask6[1])) + +proc `==`*(m1, m2: IpMask): bool {.inline.} = + ## Returns ``true`` if masks ``m1`` and ``m2`` are equal in IP family and + ## by value. + if m1.family == m2.family: + if m1.family == AddressFamily.IPv4: + result = (m1.mask4 == m2.mask4) + elif m1.family == AddressFamily.IPv6: + result = ((m1.mask6[0] == m2.mask6[0]) and (m1.mask6[1] == m2.mask6[1])) + +proc init*(t: typedesc[IpMask], family: AddressFamily, prefix: int): IpMask = + ## Initialize mask of IP family ``family`` from prefix length ``prefix``. + if family == AddressFamily.IPv4: + result = IpMask(family: AddressFamily.IPv4) + if prefix <= 0: + result.mask4 = 0x00'u32 + else: + result.mask4 = 0xFFFF_FFFF'u32 + if prefix > 0 and prefix < 32: + result.mask4 = 0xFFFF_FFFF'u32 + result.mask4 = cast[uint32](result.mask4 shl (32 - prefix)) + elif family == AddressFamily.IPv6: + result = IpMask(family: AddressFamily.IPv6) + if prefix <= 0: + result.mask6[0] = 0x00'u64 + result.mask6[1] = 0x00'u64 + elif prefix >= 128: + result.mask6[0] = 0xFFFF_FFFF_FFFF_FFFF'u64 + result.mask6[1] = 0xFFFF_FFFF_FFFF_FFFF'u64 + else: + result.mask6[0] = 0xFFFF_FFFF_FFFF_FFFF'u64 + if prefix > 64: + result.mask6[1] = 0xFFFF_FFFF_FFFF_FFFF'u64 + result.mask6[1] = result.mask6[1] shl (128 - prefix) + elif prefix == 64: + result.mask6[1] = 0x00'u64 + else: + result.mask6[0] = result.mask6[0] shl (64 - prefix) + result.mask6[1] = 0x00'u64 + result = result.toNetworkOrder() + +proc init*(t: typedesc[IpMask], netmask: TransportAddress): IpMask = + ## Initialize network mask using address ``netmask``. + if netmask.family == AddressFamily.IPv4: + result.family = netmask.family + result.mask4 = cast[ptr uint32](unsafeAddr netmask.address_v4[0])[] + elif netmask.family == AddressFamily.IPv6: + result.family = netmask.family + result.mask6[0] = cast[ptr uint64](unsafeAddr netmask.address_v6[0])[] + result.mask6[1] = cast[ptr uint64](unsafeAddr netmask.address_v6[8])[] + +proc initIp*(t: typedesc[IpMask], netmask: string): IpMask = + ## Initialize network mask using IPv4 or IPv6 address in text representation + ## ``netmask``. + ## + ## If ``netmask`` address string is invalid, result IpMask.family will be + ## set to ``AddressFamily.None``. + try: + var ip = parseIpAddress(netmask) + var tip = initTAddress(ip, Port(0)) + result = t.init(tip) + except ValueError: + discard + +proc init*(t: typedesc[IpMask], netmask: string): IpMask = + ## Initialize network mask using hexadecimal string representation + ## ``netmask``. + ## + ## If ``netmask`` mask is invalid, result IpMask.family will be set to + ## ``AddressFamily.None``. + const + hexNumbers = {'0'..'9'} + hexCapitals = {'A'..'F'} + hexLowers = {'a'..'f'} + let length = len(netmask) + if length == 8 or length == (2 + 8): + ## IPv4 mask + var offset = 0 + if length == 2 + 8: + offset = 2 + var res = IpMask(family: AddressFamily.IPv4) + var r, v: uint32 + for i in 0..<8: + if netmask[offset + i] in hexNumbers: + v = cast[uint32](ord(netmask[offset + i]) - ord('0')) + elif netmask[offset + i] in hexCapitals: + v = cast[uint32](ord(netmask[offset + i]) - ord('A') + 10) + elif netmask[offset + i] in hexLowers: + v = cast[uint32](ord(netmask[offset + i]) - ord('a') + 10) + else: + return + r = (r shl 4) or v + bigEndian32(addr res.mask4, addr r) + result = res + elif length == 32 or length == (2 + 32): + ## IPv6 mask + var offset = 0 + if length == 2 + 32: + offset = 2 + var res = IpMask(family: AddressFamily.IPv6) + for i in 0..1: + var r, v: uint64 + for i in 0..<16: + if netmask[offset + i] in hexNumbers: + v = cast[uint64](ord(netmask[offset + i]) - ord('0')) + elif netmask[offset + i] in hexCapitals: + v = cast[uint64](ord(netmask[offset + i]) - ord('A') + 10) + elif netmask[offset + i] in hexLowers: + v = cast[uint64](ord(netmask[offset + i]) - ord('a') + 10) + else: + return + r = (r shl 4) or v + offset += 16 + bigEndian64(addr res.mask6[i], addr r) + result = res + +proc toIPv6*(address: TransportAddress): TransportAddress = + ## Map IPv4 ``address`` to IPv6 address. + ## + ## If ``address`` is IPv4 address then it will be mapped as: + ## <80 bits of zeros> + <16 bits of ones> + <32-bit IPv4 address>. + ## + ## If ``address`` is IPv6 address it will be returned without any changes. + if address.family == AddressFamily.IPv4: + result = TransportAddress(family: AddressFamily.IPv6) + result.address_v6[10] = 0xFF'u8 + result.address_v6[11] = 0xFF'u8 + let data = cast[ptr uint32](unsafeAddr address.address_v4[0])[] + cast[ptr uint32](addr result.address_v6[12])[] = data + elif address.family == AddressFamily.IPv6: + result = address + +proc isV4Mapped*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is (IPv4 to IPv6) mapped address, e.g. + ## 0000:0000:0000:0000:0000:FFFF:xxxx:xxxx + ## + ## Procedure returns ``false`` if ``address`` family is IPv4. + if address.family == AddressFamily.IPv6: + let data0 = cast[ptr uint64](unsafeAddr address.address_v6[0])[] + let data1 = cast[ptr uint16](unsafeAddr address.address_v6[8])[] + let data2 = cast[ptr uint16](unsafeAddr address.address_v6[10])[] + result = (data0 == 0'u64) and (data1 == 0x00'u16) and (data2 == 0xFFFF'u16) + +proc toIPv4*(address: TransportAddress): TransportAddress = + ## Get IPv4 from (IPv4 to IPv6) mapped address. + ## + ## If ``address`` is IPv4 address it will be returned without any changes. + ## + ## If ``address`` is not IPv4 to IPv6 mapped address, then result family will + ## be set to AddressFamily.None. + if address.family == AddressFamily.IPv6: + if isV4Mapped(address): + result = TransportAddress(family: AddressFamily.IPv4) + let data = cast[ptr uint32](unsafeAddr address.address_v6[12])[] + cast[ptr uint32](addr result.address_v4[0])[] = data + elif address.family == AddressFamily.IPv4: + result = address + +proc mask*(a: TransportAddress, m: IpMask): TransportAddress = + ## Apply IP mask ``m`` to address ``a`` and return result address. + ## + ## If ``a`` family is IPv4 and ``m`` family is IPv6, masking is still + ## possible when ``m`` has ``FFFF:FFFF:FFFF:FFFF:FFFF:FFFF`` prefix. Returned + ## value will be IPv4 address. + ## + ## If ``a`` family is IPv6 and ``m`` family is IPv4, masking is still + ## possible when ``a`` holds (IPv4 to IPv6) mapped address. Returned value + ## will be IPv6 address. + ## + ## If ``a`` family is IPv4 and ``m`` family is IPv4, returned value will be + ## IPv4 address. + ## + ## If ``a`` family is IPv6 and ``m`` family is IPv6, returned value will be + ## IPv6 address. + ## + ## In all other cases returned address will have ``AddressFamily.None``. + if a.family == AddressFamily.IPv4 and m.family == AddressFamily.IPv6: + if (m.mask6[0] == 0xFFFF_FFFF_FFFF_FFFF'u64) and + (m.mask6[1] and 0xFFFF_FFFF'u64) == 0xFFFF_FFFF'u64: + result = TransportAddress(family: a.family) + let mask = cast[uint32](m.mask6[1] shr 32) + let data = cast[ptr uint32](unsafeAddr a.address_v4[0])[] + cast[ptr uint32](addr result.address_v4[0])[] = data and mask + result.port = a.port + elif a.family == AddressFamily.IPv6 and m.family == AddressFamily.IPv4: + var ip = a.toIPv4() + if ip.family == AddressFamily.IPv4: + let data = cast[ptr uint32](addr ip.address_v4[0])[] + cast[ptr uint32](addr ip.address_v4[0])[] = data and m.mask4 + result = ip.toIPv6() + result.port = a.port + elif a.family == AddressFamily.IPv4 and m.family == AddressFamily.IPv4: + result = TransportAddress(family: AddressFamily.IPv4) + let data = cast[ptr uint32](unsafeAddr a.address_v4[0])[] + cast[ptr uint32](addr result.address_v4[0])[] = data and m.mask4 + result.port = a.port + elif a.family == AddressFamily.IPv6 and m.family == AddressFamily.IPv6: + result = TransportAddress(family: AddressFamily.IPv6) + let data0 = cast[ptr uint64](unsafeAddr a.address_v6[0])[] + let data1 = cast[ptr uint64](unsafeAddr a.address_v6[8])[] + cast[ptr uint64](addr result.address_v6[0])[] = data0 and m.mask6[0] + cast[ptr uint64](addr result.address_v6[8])[] = data1 and m.mask6[1] + result.port = a.port + +proc prefix*(mask: IpMask): int = + ## Returns number of bits set `1` in IP mask ``mask``. + ## + ## Procedure returns ``-1`` if mask is not canonical, e.g. has holes with + ## ``0`` bits between ``1`` bits. + var hmask = mask.toHostOrder() + if hmask.family == AddressFamily.IPv4: + var n = hmask.mask4 + while n != 0: + if (n and 0x8000_0000'u32) == 0'u32: + result = -1 + return + n = n shl 1 + inc(result) + elif hmask.family == AddressFamily.IPv6: + if hmask.mask6[0] == 0xFFFF_FFFF_FFFF_FFFF'u64: + result += 64 + if hmask.mask6[1] == 0xFFFF_FFFF_FFFF_FFFF'u64: + result += 64: + else: + var n = hmask.mask6[1] + while n != 0: + if (n and 0x8000_0000_0000_0000'u64) == 0'u64: + result = -1 + return + n = n shl 1 + inc(result) + else: + var n = hmask.mask6[0] + while n != 0: + if (n and 0x8000_0000_0000_0000'u64) == 0'u64: + result = -1 + return + n = n shl 1 + inc(result) + if hmask.mask6[1] != 0x00'u64: + result = -1 + +proc subnetMask*(mask: IpMask): TransportAddress = + ## Returns TransportAddress representation of IP mask ``mask``. + result = TransportAddress(family: mask.family) + if mask.family == AddressFamily.IPv4: + cast[ptr uint32](addr result.address_v4[0])[] = mask.mask4 + elif mask.family == AddressFamily.IPv6: + cast[ptr uint64](addr result.address_v6[0])[] = mask.mask6[0] + cast[ptr uint64](addr result.address_v6[8])[] = mask.mask6[1] + +proc `$`*(mask: IpMask, include0x = false): string = + ## Returns hexadecimal string representation of IP mask ``mask``. + var host = mask.toHostOrder() + result = "" + if host.family == AddressFamily.IPv4: + result = if include0x: "0x" else: "" + var n = 32 + var m = host.mask4 + while n > 0: + n -= 4 + var c = cast[int]((m shr n) and 0x0F) + if c < 10: + result.add(chr(ord('0') + c)) + else: + result.add(chr(ord('A') + (c - 10))) + elif host.family == AddressFamily.IPv6: + result = if include0x: "0x" else: "" + for i in 0..1: + var n = 64 + var m = host.mask6[i] + while n > 0: + n -= 4 + var c = cast[int]((m shr n) and 0x0F) + if c < 10: + result.add(chr(ord('0') + c)) + else: + result.add(chr(ord('A') + (c - 10))) + else: + raise newException(ValueError, "Invalid mask") + +proc ip*(mask: IpMask): string = + ## Returns IP address text representation of IP mask ``mask``. + if mask.family == AddressFamily.IPv4: + var ip = IpAddress(family: IpAddressFamily.IPv4) + copyMem(addr ip.address_v4[0], unsafeAddr mask.mask4, sizeof(uint32)) + result = $ip + elif mask.family == AddressFamily.IPv6: + var ip = IpAddress(family: IpAddressFamily.IPv6) + copyMem(addr ip.address_v6[0], unsafeAddr mask.mask6[0], 16) + result = $ip + else: + raise newException(ValueError, "Invalid mask") + +proc init*(t: typedesc[IpNet], host: TransportAddress, + prefix: int): IpNet {.inline.} = + ## Initialize IP Network using host address ``host`` and prefix length + ## ``prefix``. + result.mask = IpMask.init(host.family, prefix) + result.host = host + +proc init*(t: typedesc[IpNet], host, mask: TransportAddress): IpNet {.inline.} = + ## Initialize IP Network using host address ``host`` and network mask + ## address ``mask``. + ## + ## Note that ``host`` and ``mask`` must be from the same IP family. + if host.family == mask.family: + result.mask = IpMask.init(mask) + result.host = host + +proc init*(t: typedesc[IpNet], host: TransportAddress, + mask: IpMask): IpNet {.inline.} = + ## Initialize IP Network using host address ``host`` and network mask + ## ``mask``. + result.mask = mask + result.host = host + +proc init*(t: typedesc[IpNet], network: string): IpNet = + ## Initialize IP Network from string representation in format + ##
/ or
/. + var parts = network.rsplit("/", maxsplit = 1) + var host, mhost: TransportAddress + var ipaddr: IpAddress + var mask: IpMask + var prefix: int + try: + ipaddr = parseIpAddress(parts[0]) + if ipaddr.family == IpAddressFamily.IPv4: + host = TransportAddress(family: AddressFamily.IPv4) + host.address_v4 = ipaddr.address_v4 + prefix = 32 + elif ipaddr.family == IpAddressFamily.IPv6: + host = TransportAddress(family: AddressFamily.IPv6) + host.address_v6 = ipaddr.address_v6 + prefix = 128 + if len(parts) > 1: + try: + prefix = parseInt(parts[1]) + except: + prefix = -1 + if prefix == -1: + ipaddr = parseIpAddress(parts[1]) + if ipaddr.family == IpAddressFamily.IPv4: + mhost = TransportAddress(family: AddressFamily.IPv4) + mhost.address_v4 = ipaddr.address_v4 + elif ipaddr.family == IpAddressFamily.IPv6: + mhost = TransportAddress(family: AddressFamily.IPv6) + mhost.address_v6 = ipaddr.address_v6 + mask = IpMask.init(mhost) + if mask.family != host.family: + raise newException(TransportAddressError, + "Incorrect network address!") + else: + if (ipaddr.family == IpAddressFamily.IPv4 and + (prefix < 0 or prefix > 32)) or + (ipaddr.family == IpAddressFamily.IPv6 and + (prefix < 0 or prefix > 128)): + raise newException(TransportAddressError, + "Incorrect network address!") + if prefix == -1: + result = t.init(host, mask) + else: + result = t.init(host, prefix) + except: + raise newException(TransportAddressError, "Incorrect network address!") + +proc `==`*(n1, n2: IpNet): bool {.inline.} = + ## Returns ``true`` if networks ``n1`` and ``n2`` are equal in IP family and + ## by value. + if n1.host.family == n2.host.family: + if n1.host.family == AddressFamily.IPv4: + result = (n1.host.address_v4 == n2.host.address_v4) and + (n1.mask == n2.mask) + elif n1.host.family == AddressFamily.IPv6: + result = (n1.host.address_v6 == n2.host.address_v6) and + (n1.mask == n2.mask) + +proc contains*(net: IpNet, address: TransportAddress): bool = + ## Returns ``true`` if ``address`` belongs to IP Network ``net`` + if net.host.family == address.family: + var host1 = mask(address, net.mask) + var host2 = mask(net.host, net.mask) + host2.port = host1.port + result = (host1 == host2) + +proc broadcast*(net: IpNet): TransportAddress = + ## Returns broadcast address for IP Network ``net``. + result = TransportAddress(family: net.host.family) + if result.family == AddressFamily.IPv4: + let address = cast[ptr uint32](unsafeAddr net.host.address_v4[0])[] + let mask = cast[ptr uint32](unsafeAddr net.mask.mask4)[] + cast[ptr uint32](addr result.address_v4[0])[] = address or (not(mask)) + elif result.family == AddressFamily.IPv6: + let address0 = cast[ptr uint64](unsafeAddr net.host.address_v6[0])[] + let address1 = cast[ptr uint64](unsafeAddr net.host.address_v6[8])[] + let data0 = cast[ptr uint64](unsafeAddr net.mask.mask6[0])[] + let data1 = cast[ptr uint64](unsafeAddr net.mask.mask6[1])[] + cast[ptr uint64](addr result.address_v6[0])[] = address0 or (not(data0)) + cast[ptr uint64](addr result.address_v6[8])[] = address1 or (not(data1)) + +proc subnetMask*(net: IpNet): TransportAddress = + ## Returns netmask address for IP Network ``net``. + result = TransportAddress(family: net.host.family) + if result.family == AddressFamily.IPv4: + let address = cast[ptr uint32](unsafeAddr net.mask.mask4)[] + cast[ptr uint32](addr result.address_v4[0])[] = address + elif result.family == AddressFamily.IPv6: + let address0 = cast[ptr uint64](unsafeAddr net.mask.mask6[0])[] + let address1 = cast[ptr uint64](unsafeAddr net.mask.mask6[1])[] + cast[ptr uint64](addr result.address_v6[0])[] = address0 + cast[ptr uint64](addr result.address_v6[8])[] = address1 + +proc network*(net: IpNet): TransportAddress {.inline.} = + ## Returns network address (host address masked with network mask) for + ## IP Network ``net``. + result = mask(net.host, net.mask) + +proc `and`*(address1, address2: TransportAddress): TransportAddress = + ## Bitwise ``and`` operation for ``address1 and address2``. + ## + ## Note only IPv4 and IPv6 addresses are supported. ``address1`` and + ## ``address2`` must be in equal IP family + if address1.family == address2.family: + if address1.family == AddressFamily.IPv4: + let data1 = cast[ptr uint32](unsafeAddr address1.address_v4[0])[] + let data2 = cast[ptr uint32](unsafeAddr address2.address_v4[0])[] + result = TransportAddress(family: address1.family) + cast[ptr uint32](addr result.address_v4[0])[] = data1 and data2 + elif address1.family == AddressFamily.IPv6: + let data1 = cast[ptr uint64](unsafeAddr address1.address_v6[0])[] + let data2 = cast[ptr uint64](unsafeAddr address1.address_v6[8])[] + let data3 = cast[ptr uint64](unsafeAddr address2.address_v6[0])[] + let data4 = cast[ptr uint64](unsafeAddr address2.address_v6[8])[] + result = TransportAddress(family: address1.family) + cast[ptr uint64](addr result.address_v6[0])[] = data1 and data3 + cast[ptr uint64](addr result.address_v6[8])[] = data2 and data4 + +proc `or`*(address1, address2: TransportAddress): TransportAddress = + ## Bitwise ``or`` operation for ``address1 or address2``. + ## + ## Note only IPv4 and IPv6 addresses are supported. ``address1`` and + ## ``address2`` must be in equal IP family + if address1.family == address2.family: + if address1.family == AddressFamily.IPv4: + let data1 = cast[ptr uint32](unsafeAddr address1.address_v4[0])[] + let data2 = cast[ptr uint32](unsafeAddr address2.address_v4[0])[] + result = TransportAddress(family: address1.family) + cast[ptr uint32](addr result.address_v4[0])[] = data1 or data2 + elif address1.family == AddressFamily.IPv6: + let data1 = cast[ptr uint64](unsafeAddr address1.address_v6[0])[] + let data2 = cast[ptr uint64](unsafeAddr address1.address_v6[8])[] + let data3 = cast[ptr uint64](unsafeAddr address2.address_v6[0])[] + let data4 = cast[ptr uint64](unsafeAddr address2.address_v6[8])[] + result = TransportAddress(family: address1.family) + cast[ptr uint64](addr result.address_v6[0])[] = data1 or data3 + cast[ptr uint64](addr result.address_v6[8])[] = data2 or data4 + +proc `not`*(address: TransportAddress): TransportAddress = + ## Bitwise ``not`` operation for ``address``. + if address.family == AddressFamily.IPv4: + let data = cast[ptr uint32](unsafeAddr address.address_v4[0])[] + result = TransportAddress(family: address.family) + cast[ptr uint32](addr result.address_v4[0])[] = not(data) + elif address.family == AddressFamily.IPv6: + let data1 = cast[ptr uint64](unsafeAddr address.address_v6[0])[] + let data2 = cast[ptr uint64](unsafeAddr address.address_v6[8])[] + result = TransportAddress(family: address.family) + cast[ptr uint64](addr result.address_v6[0])[] = not(data1) + cast[ptr uint64](addr result.address_v6[8])[] = not(data2) + +proc `+`*(address: TransportAddress, v: uint): TransportAddress = + ## Add to IPv4/IPv6 transport ``address`` unsigned integer ``v``. + result = TransportAddress(family: address.family) + if address.family == AddressFamily.IPv4: + var a: uint64 + let data = cast[ptr uint32](unsafeAddr address.address_v4[0]) + when system.cpuEndian == bigEndian: + a = data + else: + swapEndian32(addr a, data) + a = a + v + bigEndian32(cast[pointer](addr result.address_v4[0]), addr a) + elif address.family == AddressFamily.IPv6: + var a1, a2: uint64 + let data1 = cast[ptr uint64](unsafeAddr address.address_v6[0]) + let data2 = cast[ptr uint64](unsafeAddr address.address_v6[8]) + when system.cpuEndian == bigEndian: + a1 = data1 + a2 = data2 + else: + swapEndian64(addr a1, data1) + swapEndian64(addr a2, data2) + var a3 = a2 + v + if a3 < a2: + ## Overflow + a1 = a1 + 1 + bigEndian64(cast[pointer](addr result.address_v6[0]), addr a1) + bigEndian64(cast[pointer](addr result.address_v6[8]), addr a3) + +proc inc*(address: var TransportAddress, v: uint = 1'u) = + ## Increment IPv4/IPv6 transport ``address`` by unsigned integer ``v``. + address = address + v + +proc `$`*(net: IpNet): string = + ## Return string representation of IP network in format: + ## /. + if net.host.family == AddressFamily.IPv4: + var a = IpAddress(family: IpAddressFamily.IPv4, + address_v4: net.host.address_v4) + result = $a + result.add("/") + let prefix = net.mask.prefix() + if prefix == -1: + result.add(net.mask.ip()) + else: + result.add($prefix) + elif net.host.family == AddressFamily.IPv6: + var a = IpAddress(family: IpAddressFamily.IPv6, + address_v6: net.host.address_v6) + result = $a + result.add("/") + let prefix = net.mask.prefix() + if prefix == -1: + result.add(net.mask.ip()) + else: + result.add($prefix) + +proc isUnspecified*(address: TransportAddress): bool {.inline.} = + ## Returns ``true`` if ``address`` is not specified yet, e.g. its ``family`` + ## field is not set or equal to ``AddressFamily.None``. + if address.family == AddressFamily.None: + result = true + +proc isZero*(address: TransportAddress): bool {.inline.} = + ## Returns ``true`` if ``address`` is full of zeros, but its ``family`` is + ## not ``AddressFamily.None``. + if address.family == AddressFamily.IPv4: + result = cast[ptr uint32](unsafeAddr address.address_v4[0])[] == 0'u32 + elif address.family == AddressFamily.IPv6: + let r1 = cast[ptr uint64](unsafeAddr address.address_v6[0])[] == 0'u64 + let r2 = cast[ptr uint64](unsafeAddr address.address_v6[8])[] == 0'u64 + result = r1 and r2 + elif address.family == AddressFamily.Unix: + result = len($cast[cstring](unsafeAddr address.address_un[0])) == 0 + +proc isMulticast*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is a multicast address. + ## + ## ``IPv4``: 224.0.0.0 - 239.255.255.255 + ## + ## ``IPv6``: FF00:: - FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF + if address.family == AddressFamily.IPv4: + result = ((address.address_v4[0] and 0xF0'u8) == 0xE0'u8) + elif address.family == AddressFamily.IPv6: + result = (address.address_v6[0] == 0xFF'u8) + +proc isInterfaceLocalMulticast*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is interface local multicast address. + ## + ## ``IPv4``: N/A (always returns ``false``) + if address.family == AddressFamily.IPv6: + result = (address.address_v6[0] == 0xFF'u8) and + ((address.address_v6[1] and 0x0F'u8) == 0x01'u8) + +proc isLinkLocalMulticast*(address: TransportAddress): bool = + ## Returns ``true`` if ``address` is link local multicast address. + ## + ## ``IPv4``: 224.0.0.0 - 224.0.0.255 + if address.family == AddressFamily.IPv4: + result = (address.address_v4[0] == 224'u8) and + (address.address_v4[1] == 0'u8) and + (address.address_v4[2] == 0'u8) + elif address.family == AddressFamily.IPv6: + result = (address.address_v6[0] == 0xFF'u8) and + ((address.address_v6[1] and 0x0F'u8) == 0x02'u8) + +proc isLoopback*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is loopback address. + ## + ## ``IPv4``: 127.0.0.0 - 127.255.255.255 + ## + ## ``IPv6``: ::1 + if address.family == AddressFamily.IPv4: + result = (address.address_v4[0] == 127'u8) + elif address.family == AddressFamily.IPv6: + var test = 0 + for i in 0..<(len(address.address_v6) - 1): + test = test or cast[int](address.address_v6[i]) + result = (test == 0) and (address.address_v6[15] == 1'u8) + +proc isAnyLocal*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is a wildcard address. + ## + ## ``IPv4``: 0.0.0.0 + ## + ## ``IPv6``: :: + if address.family == AddressFamily.IPv4: + let data = cast[ptr uint32](unsafeAddr address.address_v4[0])[] + result = (data == 0'u32) + elif address.family == AddressFamily.IPv6: + let data1 = cast[ptr uint32](unsafeAddr address.address_v6[0])[] + let data2 = cast[ptr uint32](unsafeAddr address.address_v6[4])[] + let data3 = cast[ptr uint32](unsafeAddr address.address_v6[8])[] + let data4 = cast[ptr uint32](unsafeAddr address.address_v6[12])[] + result = ((data1 or data2 or data3 or data4) == 0'u32) + +proc isLinkLocal*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is link local address. + ## + ## ``IPv4``: 169.254.0.0 - 169.254.255.255 + ## + ## ``IPv6``: FE80:: - FEBF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF + if address.family == AddressFamily.IPv4: + result = (address.address_v4[0] == 169'u8) and + (address.address_v4[1] == 254'u8) + elif address.family == AddressFamily.IPv6: + result = (address.address_v6[0] == 0xFE'u8) and + ((address.address_v6[1] and 0xC0'u8) == 0x80'u8) + +proc isLinkLocalUnicast*(address: TransportAddress): bool {.inline.} = + result = isLinkLocal(address) + +proc isSiteLocal*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is site local address. + ## + ## ``IPv4``: 10.0.0.0 - 10.255.255.255, 172.16.0.0 - 172.31.255.255, + ## 192.168.0.0 - 192.168.255.255 + ## + ## ``IPv6``: FEC0:: - FEFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF + if address.family == AddressFamily.IPv4: + result = (address.address_v4[0] == 10'u8) or + ((address.address_v4[0] == 172'u8) and + ((address.address_v4[1] and 0xF0) == 16)) or + ((address.address_v4[0] == 192'u8) and + ((address.address_v4[1] == 168'u8))) + elif address.family == AddressFamily.IPv6: + result = (address.address_v6[0] == 0xFE'u8) and + ((address.address_v6[1] and 0xC0'u8) == 0xC0'u8) + +proc isGlobalMulticast*(address: TransportAddress): bool = + ## Returns ``true`` if the multicast address has global scope. + ## + ## ``IPv4``: 224.0.1.0 - 238.255.255.255 + ## + ## ``IPv6``: FF0E:: - FFFE:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF + if address.family == AddressFamily.IPv4: + result = (address.address_v4[0] >= 224'u8) and + (address.address_v4[0] <= 238'u8) and + not( + (address.address_v4[0] == 224'u8) and + (address.address_v4[1] == 0'u8) and + (address.address_v4[2] == 0'u8) + ) + elif address.family == AddressFamily.IPv6: + result = (address.address_v6[0] == 0xFF'u8) and + ((address.address_v6[1] and 0x0F'u8) == 0x0E'u8) diff --git a/chronos/transports/osnet.nim b/chronos/transports/osnet.nim new file mode 100644 index 00000000..be082cf9 --- /dev/null +++ b/chronos/transports/osnet.nim @@ -0,0 +1,1554 @@ +# +# Chronos OS utilities +# (c) Copyright 2018-Present +# Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) + +## This module implements cross-platform network interfaces list. +## Currently supported OSes are Windows, Linux, MacOS, BSD(not tested). +import algorithm +from strutils import toHex +import ipnet +export ipnet + +const + MaxAdapterAddressLength* = 8 + +type + InterfaceType* = enum + IfError = 0, # This is workaround element for ProoveInit warnings. + IfOther = 1, + IfRegular1822 = 2, + IfHdh1822 = 3, + IfDdnX25 = 4, + IfRfc877X25 = 5, + IfEthernetCsmacd = 6, + IfIso88023Csmacd = 7, + IfIso88024TokenBus = 8, + IfIso88025TokenRing = 9, + IfIso88026MAN = 10, + IfStarlan = 11, + IfProteon10Mbit = 12, + IfProteon80Mbit = 13, + IfHyperChannel = 14, + IfFddi = 15, + IfLapB = 16, + IfSdlc = 17, + IfDs1 = 18, + IfE1 = 19, + IfBasicIsdn = 20, + IfPrimaryIsdn = 21, + IfPropPoint2PointSerial = 22, + IfPpp = 23, + IfSoftwareLoopback = 24, + IfEon = 25, + IfEthernet3Mbit = 26, + IfNsip = 27, + IfSlip = 28, + IfUltra = 29, + IfDs3 = 30, + IfSip = 31, + IfFrameRelay = 32, + IfRs232 = 33, + IfPara = 34, + IfArcNet = 35, + IfArcNetPlus = 36, + IfAtm = 37, + IfMioX25 = 38, + IfSonet = 39, + IfX25Ple = 40, + IfIso88022Llc = 41, + IfLocalTalk = 42, + IfSmdsDxi = 43, + IfFrameRelayService = 44, + IfV35 = 45, + IfHssi = 46, + IfHippi = 47, + IfModem = 48, + IfAal5 = 49, + IfSonetPath = 50, + IfSonetVt = 51, + IfSmdsIcip = 52, + IfPropVirtual = 53, + IfPropMultiplexor = 54, + IfIeee80212 = 55, + IfFibreChannel = 56, + IfHippiInterface = 57, + IfFrameRelayInterconnect = 58, + IfAflane8023 = 59, + IfAflane8025 = 60, + IfCctemul = 61, + IfFastEther = 62, + IfIsdn = 63, + IfV11 = 64, + IfV36 = 65, + IfG70364K = 66, + IfG7032MB = 67, + IfQllc = 68, + IfFastEtherFx = 69, + IfChannel = 70, + IfIeee80211 = 71, + IfIbm370Parchan = 72, + IfEscon = 73, + IfDlsw = 74, + IfIsdnS = 75, + IfIsdnU = 76, + IfLapD = 77, + IfIpSwitch = 78, + IfRsrb = 79, + IfAtmLogical = 80, + IfDs0 = 81, + IfDs0Bundle = 82, + IfBsc = 83, + IfAsync = 84, + IfCnr = 85, + IfIso88025rDtr = 86, + IfEplrs = 87, + IfArap = 88, + IfPropCnls = 89, + IfHostPad = 90, + IfTermPad = 91, + IfFrameRelayMpi = 92, + IfX213 = 93, + IfAdsl = 94, + IfRadsl = 95, + IfSdsl = 96, + IfVdsl = 97, + IfIso88025Crfprint = 98, + IfMyrInet = 99, + IfVoiceEm = 100, + IfVoiceFxo = 101, + IfVoiceFxs = 102, + IfVoiceEncap = 103, + IfVoiceOverip = 104, + IfAtmDxi = 105, + IfAtmFuni = 106, + IfAtmIma = 107, + IfPppMultilinkBundle = 108, + IfIpoverCdlc = 109, + IfIpoverClaw = 110, + IfStackToStack = 111, + IfVirtualIpAddress = 112, + IfMpc = 113, + IfIpoverAtm = 114, + IfIso88025Fiber = 115, + IfTdlc = 116, + IfGigabitEthernet = 117, + IfHdlc = 118, + IfLapF = 119, + IfV37 = 120, + IfX25Mlp = 121, + IfX25HuntGroup = 122, + IfTransPhdlc = 123, + IfInterleave = 124, + IfFast = 125, + IfIp = 126, + IfDocScableMaclayer = 127, + IfDocScableDownstream = 128, + IfDocScableUpstream = 129, + IfA12MppSwitch = 130, + IfTunnel = 131, + IfCoffee = 132, + IfCes = 133, + IfAtmSubInterface = 134, + IfL2Vlan = 135, + IfL3IpVlan = 136, + IfL3IpxVlan = 137, + IfDigitalPowerline = 138, + IfMediaMailOverIp = 139, + IfDtm = 140, + IfDcn = 141, + IfIpForward = 142, + IfMsdsl = 143, + IfIeee1394 = 144, + IfIfGsn = 145, + IfDvbrccMaclayer = 146, + IfDvbrccDownstream = 147, + IfDvbrccUpstream = 148, + IfAtmVirtual = 149, + IfMplsTunnel = 150, + IfSrp = 151, + IfVoiceOverAtm = 152, + IfVoiceOverFrameRelay = 153, + IfIdsl = 154, + IfCompositeLink = 155, + IfSs7SigLink = 156, + IfPropWirelessP2p = 157, + IfFrForward = 158, + IfRfc1483 = 159, + IfUsb = 160, + IfIeee8023AdLag = 161, + IfBgpPolicyAccounting = 162, + IfFrf16MfrBundle = 163, + IfH323Gatekeeper = 164, + IfH323Proxy = 165, + IfMpls = 166, + IfMfSigLink = 167, + IfHdsl2 = 168, + IfShdsl = 169, + IfDs1Fdl = 170, + IfPos = 171, + IfDvbAsiIn = 172, + IfDvbAsiOut = 173, + IfPlc = 174, + IfNfas = 175, + IfTr008 = 176, + IfGr303Rdt = 177, + IfGr303Idt = 178, + IfIsup = 179, + IfPropDocsWirelessMaclayer = 180, + IfPropDocsWirelessDownstream = 181, + IfPropDocsWirelessUpstream = 182, + IfHiperLan2 = 183, + IfPropBwaP2mp = 184, + IfSonetOverheadChannel = 185, + IfDigitalWrapperOverheadChannel = 186, + IfAal2 = 187, + IfRadioMac = 188, + IfAtmRadio = 189, + IfImt = 190, + IfMvl = 191, + IfReachDsl = 192, + IfFrDlciEndpt = 193, + IfAtmVciEndpt = 194, + IfOpticalChannel = 195, + IfOpticalTransport = 196, + IfIeee80216Wman = 237, + IfWwanPp = 243, + IfWwanPp2 = 244, + IfIeee802154 = 259, + IfXboxWireless = 281 + + InterfaceState* = enum + StatusError = 0, # This is workaround element for ProoveInit warnings. + StatusUp, + StatusDown, + StatusTesting, + StatusUnknown, + StatusDormant, + StatusNotPresent, + StatusLowerLayerDown + + InterfaceAddress* = object + host*: TransportAddress + net*: IpNet + + NetworkInterface* = object + ifIndex*: int + ifType*: InterfaceType + name*: string + desc*: string + mtu*: int + flags*: uint64 + state*: InterfaceState + mac*: array[MaxAdapterAddressLength, byte] + maclen*: int + addresses*: seq[InterfaceAddress] + + Route* = object + ifIndex*: int + dest*: TransportAddress + source*: TransportAddress + gateway*: TransportAddress + metric*: int + +proc broadcast*(ifa: InterfaceAddress): TransportAddress {.inline.} = + ## Return broadcast address for ``ifa``. + result = ifa.net.broadcast() + +proc network*(ifa: InterfaceAddress): TransportAddress {.inline.} = + ## Return network address for ``ifa``. + result = ifa.net.network() + +proc netmask*(ifa: InterfaceAddress): TransportAddress {.inline.} = + ## Return network mask for ``ifa``. + result = ifa.net.subnetMask() + +proc init*(ift: typedesc[InterfaceAddress], address: TransportAddress, + prefix: int): InterfaceAddress = + ## Initialize ``InterfaceAddress`` using ``address`` and prefix length + ## ``prefix``. + result.host = address + result.net = IpNet.init(address, prefix) + +proc `$`*(ifa: InterfaceAddress): string {.inline.} = + ## Return string representation of ``ifa``. + if ifa.host.family == AddressFamily.IPv4: + result = $ifa.net + elif ifa.host.family == AddressFamily.IPv6: + result = $ifa.net + +proc `$`*(iface: NetworkInterface): string = + ## Return string representation of network interface ``iface``. + result = $iface.ifIndex + if len(result) == 1: + result.add(". ") + else: + result.add(". ") + result.add(iface.name) + when defined(windows): + result.add(" [") + result.add(iface.desc) + result.add("]") + result.add(": flags = ") + result.add($iface.flags) + result.add(" mtu ") + result.add($iface.mtu) + result.add(" state ") + result.add($iface.state) + result.add("\n ") + result.add($iface.ifType) + result.add(" ") + if iface.maclen > 0: + for i in 0..= int(sizeof(NlMsgHeader))) and + (nlh.nlmsg_len >= uint32(sizeof(NlMsgHeader))) and + (nlh.nlmsg_len <= uint32(length)) + + proc NLMSG_NEXT(nlh: ptr NlMsgHeader, + length: var int): ptr NlMsgHeader {.inline.} = + length = length - int(NLMSG_ALIGN(uint(nlh.nlmsg_len))) + result = cast[ptr NlMsgHeader](cast[uint](nlh) + + cast[uint](NLMSG_ALIGN(uint(nlh.nlmsg_len)))) + + proc NLMSG_DATA(nlh: ptr NlMsgHeader): ptr byte {.inline.} = + result = cast[ptr byte](cast[uint](nlh) + NLMSG_LENGTH(0)) + + proc NLMSG_TAIL(nlh: ptr NlMsgHeader): ptr byte {.inline.} = + cast[ptr byte](cast[uint](nlh) + NLMSG_ALIGN(uint(nlh.nlmsg_len))) + + template RTA_ALIGN*(length: uint): uint = + (length + RTA_ALIGNTO - 1) and not(RTA_ALIGNTO - 1) + + template RTA_HDRLEN(): int = + int(RTA_ALIGN(uint(sizeof(RtAttr)))) + + template RTA_LENGTH(length: int): uint = + uint(RTA_HDRLEN()) + uint(length) + + template RTA_PAYLOAD*(length: uint): uint = + length - RTA_LENGTH(0) + + proc IFLA_RTA(r: ptr byte): ptr RtAttr {.inline.} = + cast[ptr RtAttr](cast[uint](r) + NLMSG_ALIGN(uint(sizeof(IfInfoMessage)))) + + proc IFA_RTA(r: ptr byte): ptr RtAttr {.inline.} = + cast[ptr RtAttr](cast[uint](r) + NLMSG_ALIGN(uint(sizeof(IfAddrMessage)))) + + proc RT_RTA(r: ptr byte): ptr RtAttr {.inline.} = + cast[ptr RtAttr](cast[uint](r) + NLMSG_ALIGN(uint(sizeof(RtMessage)))) + + proc RTA_OK(rta: ptr RtAttr, length: int): bool {.inline.} = + result = length >= sizeof(RtAttr) and + rta.rta_len >= cushort(sizeof(RtAttr)) and + rta.rta_len <= cushort(length) + + proc RTA_NEXT(rta: ptr RtAttr, length: var int): ptr RtAttr {.inline.} = + length = length - int(RTA_ALIGN(uint(rta.rta_len))) + result = cast[ptr RtAttr](cast[uint](rta) + + cast[uint](RTA_ALIGN(uint(rta.rta_len)))) + + proc RTA_DATA(rta: ptr RtAttr): ptr byte {.inline.} = + result = cast[ptr byte](cast[uint](rta) + RTA_LENGTH(0)) + + proc toInterfaceState(it: cint, flags: cuint): InterfaceState {.inline.} = + case it + of 1: + result = StatusNotPresent + of 2: + result = StatusDown + of 3: + result = StatusLowerLayerDown + of 4: + result = StatusTesting + of 5: + result = StatusDormant + of 6: + result = StatusUp + else: + result = StatusUnknown + + proc toInterfaceType(ft: uint32): InterfaceType {.inline.} = + case ft + of ARPHRD_ETHER, ARPHRD_EETHER: + result = IfEthernetCsmacd + of ARPHRD_LOOPBACK: + result = IfSoftwareLoopback + of 787..799: + result = IfFibreChannel + of ARPHRD_PPP: + result = IfPpp + of ARPHRD_SLIP, ARPHRD_CSLIP, ARPHRD_SLIP6, ARPHRD_CSLIP6: + result = IfSlip + of ARPHRD_IEEE1394: + result = IfIeee1394 + of ARPHRD_IEEE80211, ARPHRD_IEEE80211_PRISM, ARPHRD_IEEE80211_RADIOTAP: + result = IfIeee80211 + of ARPHRD_ATM: + result = IfAtm + of ARPHRD_HDLC: + result = IfHdlc + of ARPHRD_HIPPI: + result = IfHippiInterface + of ARPHRD_ARCNET: + result = IfArcNet + of ARPHRD_LAPB: + result = IfLapB + of ARPHRD_FRAD: + result = IfFrameRelay + else: + result = IfOther + + proc createNetlinkSocket(pid: Pid): SocketHandle = + var address: SockAddr_nl + address.family = cushort(AF_NETLINK) + address.groups = 0 + address.pid = cast[uint32](pid) + result = posix.socket(AF_NETLINK, posix.SOCK_DGRAM, NETLINK_ROUTE) + if result != SocketHandle(-1): + if posix.bindSocket(result, cast[ptr SockAddr](addr address), + Socklen(sizeof(SockAddr_nl))) != 0: + discard posix.close(result) + result = SocketHandle(-1) + + proc sendLinkMessage(fd: SocketHandle, pid: Pid, seqno: uint32, + ntype: uint16, nflags: uint16): bool = + var + rmsg: Tmsghdr + iov: IOVec + req: NLReq + address: SockAddr_nl + address.family = cushort(AF_NETLINK) + req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(RtGenMsg)) + req.hdr.nlmsg_type = ntype + req.hdr.nlmsg_flags = nflags + req.hdr.nlmsg_seq = seqno + req.hdr.nlmsg_pid = cast[uint32](pid) + req.msg.rtgen_family = byte(AF_PACKET) + iov.iov_base = cast[pointer](addr req) + iov.iov_len = cast[csize](req.hdr.nlmsg_len) + rmsg.msg_iov = addr iov + rmsg.msg_iovlen = 1 + rmsg.msg_name = cast[pointer](addr address) + rmsg.msg_namelen = Socklen(sizeof(SockAddr_nl)) + let res = posix.sendmsg(fd, addr rmsg, 0) + if res == iov.iov_len: + result = true + + proc sendRouteMessage(fd: SocketHandle, pid: Pid, seqno: uint32, + ntype: uint16, nflags: uint16, + dest: TransportAddress): bool = + var + rmsg: Tmsghdr + iov: IOVec + address: SockAddr_nl + buffer: array[64, byte] + + var req = cast[ptr NlRouteReq](addr buffer[0]) + + address.family = cushort(AF_NETLINK) + req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(RtMessage)) + req.hdr.nlmsg_type = ntype + req.hdr.nlmsg_flags = nflags + req.hdr.nlmsg_seq = seqno + req.hdr.nlmsg_pid = cast[uint32](pid) + + var attr = cast[ptr RtAttr](NLMSG_TAIL(addr req.hdr)) + + req.msg.rtm_flags = RTM_F_LOOKUP_TABLE + attr.rta_type = RTA_DST + if dest.family == AddressFamily.IPv4: + req.msg.rtm_family = byte(posix.AF_INET) + attr.rta_len = cast[cushort](RTA_LENGTH(4)) + copyMem(RTA_DATA(attr), cast[ptr byte](unsafeAddr dest.address_v4[0]), 4) + req.hdr.nlmsg_len = uint32(NLMSG_ALIGN(uint(req.hdr.nlmsg_len)) + + RTA_ALIGN(uint(attr.rta_len))) + req.msg.rtm_dst_len = 4 * 8 + elif dest.family == AddressFamily.IPv6: + req.msg.rtm_family = byte(posix.AF_INET6) + attr.rta_len = cast[cushort](RTA_LENGTH(16)) + copyMem(RTA_DATA(attr), cast[ptr byte](unsafeAddr dest.address_v6[0]), 16) + req.hdr.nlmsg_len = uint32(NLMSG_ALIGN(uint(req.hdr.nlmsg_len)) + + RTA_ALIGN(uint(attr.rta_len))) + req.msg.rtm_dst_len = 16 * 8 + + iov.iov_base = cast[pointer](addr buffer[0]) + iov.iov_len = cast[csize](req.hdr.nlmsg_len) + rmsg.msg_iov = addr iov + rmsg.msg_iovlen = 1 + rmsg.msg_name = cast[pointer](addr address) + rmsg.msg_namelen = Socklen(sizeof(SockAddr_nl)) + let res = posix.sendmsg(fd, addr rmsg, 0) + if res == iov.iov_len: + result = true + + proc readNetlinkMessage(fd: SocketHandle, data: var seq[byte]): bool = + var + rmsg: Tmsghdr + iov: IOVec + address: SockAddr_nl + data.setLen(IFLIST_REPLY_BUFFER) + iov.iov_base = cast[pointer](addr data[0]) + iov.iov_len = IFLIST_REPLY_BUFFER + rmsg.msg_iov = addr iov + rmsg.msg_iovlen = 1 + rmsg.msg_name = cast[pointer](addr address) + rmsg.msg_namelen = SockLen(sizeof(SockAddr_nl)) + var length = posix.recvmsg(fd, addr rmsg, 0) + if length >= 0: + data.setLen(length) + result = true + else: + data.setLen(0) + + proc processLink(msg: ptr NlMsgHeader): NetworkInterface = + var iface: ptr IfInfoMessage + var attr: ptr RtAttr + var length: int + + iface = cast[ptr IfInfoMessage](NLMSG_DATA(msg)) + length = int(msg.nlmsg_len) - int(NLMSG_LENGTH(sizeof(IfInfoMessage))) + + attr = IFLA_RTA(cast[ptr byte](iface)) + result.ifType = toInterfaceType(iface.ifi_type) + result.ifIndex = iface.ifi_index + result.flags = cast[uint64](iface.ifi_flags) + + while RTA_OK(attr, length): + if attr.rta_type == IFLA_IFNAME: + var p = cast[cstring](RTA_DATA(attr)) + result.name = $p + elif attr.rta_type == IFLA_ADDRESS: + var p = cast[ptr byte](RTA_DATA(attr)) + var plen = min(int(RTA_PAYLOAD(uint(attr.rta_len))), len(result.mac)) + copyMem(addr result.mac[0], p, plen) + result.maclen = plen + elif attr.rta_type == IFLA_MTU: + var p = cast[ptr uint32](RTA_DATA(attr)) + result.mtu = cast[int](p[]) + elif attr.rta_type == IFLA_OPERSTATE: + var p = cast[ptr byte](RTA_DATA(attr)) + result.state = toInterfaceState(cast[cint](p[]), iface.ifi_flags) + attr = RTA_NEXT(attr, length) + + proc getAddress(f: int, p: pointer): TransportAddress {.inline.} = + if f == posix.AF_INET: + result = TransportAddress(family: AddressFamily.IPv4) + copyMem(addr result.address_v4[0], p, len(result.address_v4)) + elif f == posix.AF_INET6: + result = TransportAddress(family: AddressFamily.IPv6) + copyMem(addr result.address_v6[0], p, len(result.address_v6)) + + proc processAddress(msg: ptr NlMsgHeader): NetworkInterface = + var iaddr: ptr IfAddrMessage + var attr: ptr RtAttr + var length: int + + iaddr = cast[ptr IfAddrMessage](NLMSG_DATA(msg)) + length = int(msg.nlmsg_len) - int(NLMSG_LENGTH(sizeof(IfAddrMessage))) + + attr = IFA_RTA(cast[ptr byte](iaddr)) + + let family = cast[int](iaddr.ifa_family) + result.ifIndex = cast[int](iaddr.ifa_index) + + var address, local: TransportAddress + + while RTA_OK(attr, length): + if attr.rta_type == IFA_LOCAL: + local = getAddress(family, cast[pointer](RTA_DATA(attr))) + elif attr.rta_type == IFA_ADDRESS: + address = getAddress(family, cast[pointer](RTA_DATA(attr))) + attr = RTA_NEXT(attr, length) + + if local.family != AddressFamily.None: + address = local + if len(result.addresses) == 0: + result.addresses = newSeq[InterfaceAddress]() + let prefixLength = cast[int](iaddr.ifa_prefixlen) + let ifaddr = InterfaceAddress.init(address, prefixLength) + result.addresses.add(ifaddr) + + proc processRoute(msg: ptr NlMsgHeader): Route = + var rtmsg = cast[ptr RtMessage](NLMSG_DATA(msg)) + var length = int(msg.nlmsg_len) - int(NLMSG_LENGTH(sizeof(RtMessage))) + var attr = RT_RTA(cast[ptr byte](rtmsg)) + while RTA_OK(attr, length): + if attr.rta_type == RTA_DST: + result.dest = getAddress(int(rtmsg.rtm_family), + cast[pointer](RTA_DATA(attr))) + elif attr.rta_type == RTA_GATEWAY: + result.gateway = getAddress(int(rtmsg.rtm_family), + cast[pointer](RTA_DATA(attr))) + elif attr.rta_type == RTA_OIF: + var oid: uint32 + copyMem(addr oid, RTA_DATA(attr), sizeof(uint32)) + result.ifIndex = int(oid) + elif attr.rta_type == RTA_PREFSRC: + result.source = getAddress(int(rtmsg.rtm_family), + cast[pointer](RTA_DATA(attr))) + attr = RTA_NEXT(attr, length) + + proc getRoute(netfd: SocketHandle, pid: Pid, + address: TransportAddress): Route = + if not sendRouteMessage(netfd, pid, 1, RTM_GETROUTE, + NLM_F_REQUEST, address): + return + var data = newSeq[byte]() + while true: + if not readNetlinkMessage(netfd, data): + break + var length = len(data) + var msg = cast[ptr NlMsgHeader](addr data[0]) + var endflag = false + while NLMSG_OK(msg, length): + if msg.nlmsg_type == NLMSG_ERROR: + endflag = true + break + else: + result = processRoute(msg) + endflag = true + break + msg = NLMSG_NEXT(msg, length) + if endflag: + break + + proc getLinks(netfd: SocketHandle, pid: Pid): seq[NetworkInterface] = + if not sendLinkMessage(netfd, pid, 1, RTM_GETLINK, + NLM_F_REQUEST or NLM_F_DUMP): + return + var data = newSeq[byte]() + result = newSeq[NetworkInterface]() + while true: + if not readNetlinkMessage(netfd, data): + break + var length = len(data) + if length == 0: + break + var msg = cast[ptr NlMsgHeader](addr data[0]) + var endflag = false + while NLMSG_OK(msg, length): + if msg.nlmsg_type == NLMSG_DONE: + endflag = true + break + elif msg.nlmsg_type == NLMSG_ERROR: + endflag = true + break + else: + var iface = processLink(msg) + result.add(iface) + msg = NLMSG_NEXT(msg, length) + if endflag: + break + + proc getAddresses(netfd: SocketHandle, pid: Pid, + ifaces: var seq[NetworkInterface]) = + if not sendLinkMessage(netfd, pid, 2, RTM_GETADDR, + NLM_F_REQUEST or NLM_F_DUMP): + return + var data = newSeq[byte]() + while true: + if not readNetlinkMessage(netfd, data): + break + var length = len(data) + if length == 0: + break + var msg = cast[ptr NlMsgHeader](addr data[0]) + var endflag = false + while NLMSG_OK(msg, length): + if msg.nlmsg_type == NLMSG_DONE: + endflag = true + break + elif msg.nlmsg_type == NLMSG_ERROR: + endflag = true + break + else: + var iface = processAddress(msg) + for i in 0..", + pure, final.} = object + ifa_next {.importc: "ifa_next".}: ptr IfAddrs + ifa_name {.importc: "ifa_name".}: ptr cchar + ifa_flags {.importc: "ifa_flags".}: cuint + ifa_addr {.importc: "ifa_addr".}: ptr SockAddr + ifa_netmask {.importc: "ifa_netmask".}: ptr SockAddr + ifa_dstaddr {.importc: "ifa_dstaddr".}: ptr SockAddr + ifa_data {.importc: "ifa_data".}: pointer + + PIfAddrs = ptr IfAddrs + + IfData {.importc: "struct if_data", header: "", + pure, final.} = object + ifi_type {.importc: "ifi_type".}: byte + ifi_typelen {.importc: "ifi_typelen".}: byte + ifi_physical {.importc: "ifi_physical".}: byte + ifi_addrlen {.importc: "ifi_addrlen".}: byte + ifi_hdrlen {.importc: "ifi_hdrlen".}: byte + ifi_recvquota {.importc: "ifi_recvquota".}: byte + ifi_xmitquota {.importc: "ifi_xmitquota".}: byte + ifi_unused1 {.importc: "ifi_unused1".}: byte + ifi_mtu {.importc: "ifi_mtu".}: uint32 + ifi_metric {.importc: "ifi_metric".}: uint32 + ifi_baudrate {.importc: "ifi_baudrate".}: uint32 + ifi_ipackets {.importc: "ifi_ipackets".}: uint32 + ifi_ierrors {.importc: "ifi_ierrors".}: uint32 + ifi_opackets {.importc: "ifi_opackets".}: uint32 + ifi_oerrors {.importc: "ifi_oerrors".}: uint32 + ifi_collisions {.importc: "ifi_collisions".}: uint32 + ifi_ibytes {.importc: "ifi_ibytes".}: uint32 + ifi_obytes {.importc: "ifi_obytes".}: uint32 + ifi_imcasts {.importc: "ifi_imcasts".}: uint32 + ifi_omcasts {.importc: "ifi_omcasts".}: uint32 + ifi_iqdrops {.importc: "ifi_iqdrops".}: uint32 + ifi_noproto {.importc: "ifi_noproto".}: uint32 + ifi_recvtiming {.importc: "ifi_recvtiming".}: uint32 + ifi_xmittiming {.importc: "ifi_xmittiming".}: uint32 + ifi_lastchange {.importc: "ifi_lastchange".}: Timeval + ifi_unused2 {.importc: "ifi_unused2".}: uint32 + ifi_hwassist {.importc: "ifi_hwassist".}: uint32 + ifi_reserved1 {.importc: "ifi_reserved1".}: uint32 + ifi_reserved2 {.importc: "ifi_reserved2".}: uint32 + + SockAddr_dl = object + sdl_len: byte + sdl_family: byte + sdl_index: uint16 + sdl_type: byte + sdl_nlen: byte + sdl_alen: byte + sdl_slen: byte + sdl_data: array[12, byte] + + RtMetrics = object + rmx_locks: uint32 + rmx_mtu: uint32 + rmx_hopcount: uint32 + rmx_expire: int32 + rmx_recvpipe: uint32 + rmx_sendpipe: uint32 + rmx_ssthresh: uint32 + rmx_rtt: uint32 + rmx_rttvar: uint32 + rmx_pksent: uint32 + rmx_state: uint32 + rmx_filler: array[3, uint32] + + RtMsgHeader = object + rtm_msglen: uint16 + rtm_version: byte + rtm_type: byte + rtm_index: uint16 + rtm_flags: cint + rtm_addrs: cint + rtm_pid: Pid + rtm_seq: cint + rtm_errno: cint + rtm_use: cint + rtm_inits: uint32 + rtm_rmx: RtMetrics + + RtMessage = object + rtm: RtMsgHeader + space: array[512, byte] + + BSDSockAddrHeader = object + sa_len: byte + sa_family: byte + + proc getIfAddrs(ifap: ptr PIfAddrs): cint {.importc: "getifaddrs", + header: """#include + #include + #include """.} + proc freeIfAddrs(ifap: ptr IfAddrs) {.importc: "freeifaddrs", + header: """#include + #include + #include """.} + + proc toInterfaceType(f: byte): InterfaceType {.inline.} = + var ft = cast[int](f) + if (ft >= 1 and ft <= 196) or (ft == 237) or (ft == 243) or (ft == 244): + result = cast[InterfaceType](ft) + else: + result = IfOther + + proc toInterfaceState(f: cuint): InterfaceState {.inline.} = + if (f and IFF_RUNNING) != 0 and (f and IFF_UP) != 0: + result = StatusUp + else: + result = StatusDown + + proc getInterfaces*(): seq[NetworkInterface] = + ## Return list of available interfaces. + var ifap: ptr IfAddrs + let res = getIfAddrs(addr ifap) + if res == 0: + result = newSeq[NetworkInterface]() + while not isNil(ifap): + var iface: NetworkInterface + var ifaddress: InterfaceAddress + + iface.name = $ifap.ifa_name + iface.flags = cast[uint64](ifap.ifa_flags) + var i = 0 + while i < len(result): + if result[i].name == iface.name: + break + inc(i) + if i == len(result): + result.add(iface) + + if not isNil(ifap.ifa_addr): + let family = cast[int](ifap.ifa_addr.sa_family) + if family == AF_LINK: + var data = cast[ptr IfData](ifap.ifa_data) + var link = cast[ptr SockAddr_dl](ifap.ifa_addr) + result[i].ifIndex = cast[int](link.sdl_index) + let nlen = cast[int](link.sdl_nlen) + if nlen < len(link.sdl_data): + let minsize = min(cast[int](link.sdl_alen), len(result[i].mac)) + copyMem(addr result[i].mac[0], addr link.sdl_data[nlen], minsize) + result[i].maclen = cast[int](link.sdl_alen) + result[i].ifType = toInterfaceType(data.ifi_type) + result[i].state = toInterfaceState(ifap.ifa_flags) + result[i].mtu = cast[int](data.ifi_mtu) + elif family == posix.AF_INET: + fromSAddr(cast[ptr Sockaddr_storage](ifap.ifa_addr), + Socklen(sizeof(SockAddr_in)), ifaddress.host) + elif family == posix.AF_INET6: + fromSAddr(cast[ptr Sockaddr_storage](ifap.ifa_addr), + Socklen(sizeof(SockAddr_in6)), ifaddress.host) + if not isNil(ifap.ifa_netmask): + var na: TransportAddress + var slen: Socklen + var family = cast[cint](ifap.ifa_netmask.sa_family) + if family == posix.AF_INET: + fromSAddr(cast[ptr Sockaddr_storage](ifap.ifa_netmask), + Socklen(sizeof(SockAddr_in)), na) + elif family == posix.AF_INET6: + fromSAddr(cast[ptr Sockaddr_storage](ifap.ifa_netmask), + Socklen(sizeof(SockAddr_in6)), na) + ifaddress.net = IpNet.init(ifaddress.host, na) + + if ifaddress.host.family != AddressFamily.None: + if len(result[i].addresses) == 0: + result[i].addresses = newSeq[InterfaceAddress]() + result[i].addresses.add(ifaddress) + ifap = ifap.ifa_next + + sort(result, cmp) + freeIfAddrs(ifap) + + proc sasize(data: openarray[byte]): int = + # SA_SIZE() template. Taken from FreeBSD net/route.h:1.63 + if len(data) > 0: + if data[0] == 0x00'u8: + result = sizeof(uint32) + else: + result = 1 + (int(data[0] - 1) or (sizeof(uint32) - 1)) + + proc getBestRoute*(address: TransportAddress): Route = + ## Return best applicable OS route, which will be used for connecting to + ## address ``address``. + var sock: cint + var msg: RtMessage + var pid = posix.getpid() + + if address.family notin {AddressFamily.IPv4, AddressFamily.IPv6}: + return + + if address.family == AddressFamily.IPv4: + sock = cint(posix.socket(PF_ROUTE, posix.SOCK_RAW, posix.AF_INET)) + elif address.family == AddressFamily.IPv6: + sock = cint(posix.socket(PF_ROUTE, posix.SOCK_RAW, posix.AF_INET6)) + + if sock != -1: + var sastore: Sockaddr_storage + var salen: Socklen + address.toSAddr(sastore, salen) + # We doing this trick because Nim's posix declaration of Sockaddr_storage + # is not compatible with BSD version. First byte in BSD version is length + # of Sockaddr structure, and second byte is family code. + copyMem(addr msg.space[0], addr sastore, int(salen)) + msg.rtm.rtm_type = RTM_GET + msg.rtm.rtm_flags = RTF_UP or RTF_GATEWAY + msg.rtm.rtm_version = RTM_VERSION + msg.rtm.rtm_seq = 0xCAFE + msg.rtm.rtm_addrs = RTA_DST + msg.space[0] = cast[byte](salen) + msg.rtm.rtm_msglen = uint16(sizeof(RtMessage)) + var res = posix.write(sock, addr msg, sizeof(RtMessage)) + if res >= 0: + while true: + res = posix.read(sock, addr msg, sizeof(RtMessage)) + if res >= 0 and msg.rtm.rtm_pid == pid and msg.rtm.rtm_seq == 0xCAFE: + break + if res >= 0 and msg.rtm.rtm_version == RTM_VERSION and + msg.rtm.rtm_errno == 0: + result.ifIndex = int(msg.rtm.rtm_index) + var so = 0 + var eo = len(msg.space) - 1 + for i in 0..<2: + let mask = 1 shl i + if (msg.rtm.rtm_addrs and mask) != 0: + var saddr = cast[ptr Sockaddr_storage](addr msg.space[so]) + let size = sasize(msg.space.toOpenArray(so, eo)) + if mask == RTA_DST: + fromSAddr(saddr, Socklen(size), result.dest) + elif mask == RTA_GATEWAY: + fromSAddr(saddr, Socklen(size), result.gateway) + so += size + + if result.dest.isZero(): + result.dest = address + var interfaces = getInterfaces() + for item in interfaces: + if result.ifIndex == item.ifIndex: + for a in item.addresses: + if a.host.family == address.family: + result.source = a.host + break + discard posix.close(sock) + +elif defined(windows): + import winlean, dynlib + + const + IfOperStatusUp = cint(1) + IfOperStatusDown = cint(2) + IfOperStatusTesting = cint(3) + IfOperStatusUnknown = cint(4) + IfOperStatusDormant = cint(5) + IfOperStatusNotPresent = cint(6) + IfOperStatusLowerLayerDown = cint(7) + + IpPrefixOriginOther = cint(0) + IpPrefixOriginManual = cint(1) + IpPrefixOriginWellKnown = cint(2) + IpPrefixOriginDhcp = cint(3) + IpPrefixOriginRouterAdvertisement = cint(4) + IpPrefixOriginUnchanged = cint(1) shl 4 + + IpSuffixOriginOther = cint(0) + IpSuffixOriginManual = cint(1) + IpSuffixOriginWellKnown = cint(2) + IpSuffixOriginDhcp = cint(3) + IpSuffixOriginLinkLayerAddress = cint(4) + IpSuffixOriginRandom = cint(5) + IpSuffixOriginUnchanged = cint(1) shl 4 + + IpDadStateInvalid = cint(0) + IpDadStateTentative = cint(1) + IpDadStateDuplicate = cint(2) + IpDadStateDeprecated = cint(3) + IpDadStatePreferred = cint(4) + + WorkBufferSize = 16384'u32 + MaxTries = 3 + + AF_UNSPEC = 0x00'u32 + + GAA_FLAG_INCLUDE_PREFIX = 0x0010'u32 + + CP_UTF8 = 65001'u32 + + ERROR_BUFFER_OVERFLOW* = 111'u32 + ERROR_SUCCESS* = 0'u32 + + type + WCHAR = distinct uint16 + + SocketAddress = object + lpSockaddr: ptr SockAddr + iSockaddrLength: cint + + IpAdapterUnicastAddressXpLh = object + length: uint32 + flags: uint32 + next: ptr IpAdapterUnicastAddressXpLh + address: SocketAddress + prefixOrigin: cint + suffixOrigin: cint + dadState: cint + validLifetime: uint32 + preferredLifetime: uint32 + leaseLifetime: uint32 + onLinkPrefixLength: byte # This field is available only from Vista + + IpAdapterAnycastAddressXp = object + length: uint32 + flags: uint32 + next: ptr IpAdapterAnycastAddressXp + address: SocketAddress + + IpAdapterMulticastAddressXp = object + length: uint32 + flags: uint32 + next: ptr IpAdapterMulticastAddressXp + address: SocketAddress + + IpAdapterDnsServerAddressXp = object + length: uint32 + flags: uint32 + next: ptr IpAdapterDnsServerAddressXp + address: SocketAddress + + IpAdapterPrefixXp = object + length: uint32 + flags: uint32 + next: ptr IpAdapterPrefixXp + address: SocketAddress + prefixLength: uint32 + + IpAdapterAddressesXp = object + length: uint32 + ifIndex: uint32 + next: ptr IpAdapterAddressesXp + adapterName: cstring + unicastAddress: ptr IpAdapterUnicastAddressXpLh + anycastAddress: ptr IpAdapterAnycastAddressXp + multicastAddress: ptr IpAdapterMulticastAddressXp + dnsServerAddress: ptr IpAdapterDnsServerAddressXp + dnsSuffix: ptr WCHAR + description: ptr WCHAR + friendlyName: ptr WCHAR + physicalAddress: array[MaxAdapterAddressLength, byte] + physicalAddressLength: uint32 + flags: uint32 + mtu: uint32 + ifType: uint32 + operStatus: cint + ipv6IfIndex: uint32 + zoneIndices: array[16, uint32] + firstPrefix: ptr IpAdapterPrefixXp + + MibIpForwardRow = object + dwForwardDest: uint32 + dwForwardMask: uint32 + dwForwardPolicy: uint32 + dwForwardNextHop: uint32 + dwForwardIfIndex: uint32 + dwForwardType: uint32 + dwForwardProto: uint32 + dwForwardAge: uint32 + dwForwardNextHopAS: uint32 + dwForwardMetric1: uint32 + dwForwardMetric2: uint32 + dwForwardMetric3: uint32 + dwForwardMetric4: uint32 + dwForwardMetric5: uint32 + + NET_LUID = object + value: uint64 + info: uint64 + + SOCKADDR_INET {.union.} = object + ipv4: Sockaddr_in + ipv6: Sockaddr_in6 + si_family: uint16 + + IPADDRESS_PREFIX = object + prefix: SOCKADDR_INET + prefixLength: uint8 + + MibIpForwardRow2 = object + interfaceLuid: uint64 + interfaceIndex: uint32 + destinationPrefix: IPADDRESS_PREFIX + nextHop: SOCKADDR_INET + sitePrefixLength: byte + validLifetime: uint32 + preferredLifetime: uint32 + metric: uint32 + protocol: uint32 + loopback: bool + autoconfigureAddress: bool + publish: bool + immortal: bool + age: uint32 + origin: uint32 + + GETBESTROUTE2 = proc(InterfaceLuid: ptr uint64, InterfaceIndex: uint32, + SourceAddress: ptr SOCKADDR_INET, + DestinationAddress: ptr SOCKADDR_INET, + AddressSortOptions: uint32, + BestRoute: ptr MibIpForwardRow2, + BestSourceAddress: ptr SOCKADDR_INET): DWORD {. + gcsafe, stdcall.} + + proc toInterfaceType(ft: uint32): InterfaceType {.inline.} = + if (ft >= 1'u32 and ft <= 196'u32) or + (ft == 237) or (ft == 243) or (ft == 244) or (ft == 259) or (ft == 281): + result = cast[InterfaceType](ft) + else: + result = IfOther + + proc toInterfaceState(it: cint): InterfaceState {.inline.} = + if it >= 1 and it <= 7: + result = cast[InterfaceState](it) + else: + result = StatusUnknown + + proc GetAdaptersAddresses(family: uint32, flags: uint32, reserved: pointer, + addresses: ptr IpAdapterAddressesXp, + sizeptr: ptr uint32): uint32 {. + stdcall, dynlib: "iphlpapi", importc: "GetAdaptersAddresses".} + + proc WideCharToMultiByte(CodePage: uint32, dwFlags: uint32, + lpWideCharStr: ptr WCHAR, cchWideChar: cint, + lpMultiByteStr: ptr char, cbMultiByte: cint, + lpDefaultChar: ptr char, + lpUsedDefaultChar: ptr uint32): cint + {.stdcall, dynlib: "kernel32.dll", importc: "WideCharToMultiByte".} + + proc getBestRouteXp(dwDestAddr: uint32, dwSourceAddr: uint32, + pBestRoute: ptr MibIpForwardRow): uint32 {. + stdcall, dynlib: "iphlpapi", importc: "GetBestRoute".} + + proc `$`(bstr: ptr WCHAR): string = + var buffer: char + var count = WideCharToMultiByte(CP_UTF8, 0, bstr, -1, addr(buffer), 0, + nil, nil) + if count > 0: + result = newString(count + 8) + let res = WideCharToMultiByte(CP_UTF8, 0, bstr, -1, addr(result[0]), + count, nil, nil) + if res > 0: + result.setLen(res - 1) + else: + result.setLen(0) + + proc isVista(): bool = + var ver: OSVERSIONINFO + ver.dwOSVersionInfoSize = DWORD(sizeof(ver)) + let res = getVersionExW(addr(ver)) + if res == 0: + result = false + else: + result = (ver.dwMajorVersion >= 6) + + proc toIPv6(a: TransportAddress): TransportAddress = + ## IPv4-mapped addresses are formed by: + ## <80 bits of zeros> + <16 bits of ones> + <32-bit IPv4 address>. + if a.family == AddressFamily.IPv4: + result = TransportAddress(family: AddressFamily.IPv6) + result.address_v6[10] = 0xFF'u8 + result.address_v6[11] = 0xFF'u8 + copyMem(addr result.address_v6[12], unsafeAddr a.address_v4[0], 4) + elif a.family == AddressFamily.IPv6: + result = a + + proc ipMatchPrefix(number, prefix: TransportAddress, nbits: int): bool = + var num6, prefix6: TransportAddress + if number.family == AddressFamily.IPv4: + num6 = toIPv6(number) + else: + num6 = number + if prefix.family == AddressFamily.IPv4: + prefix6 = toIPv6(number) + else: + prefix6 = prefix + var bytesCount = nbits div 8 + var bitsCount = nbits mod 8 + for i in 0..= 0: + result.net = IpNet.init(result.host, prefixLength) + else: + let prefixLength = cast[int](ifunic.onLinkPrefixLength) + if prefixLength >= 0: + result.net = IpNet.init(result.host, prefixLength) + + proc getInterfaces*(): seq[NetworkInterface] = + ## Return list of network interfaces. + result = newSeq[NetworkInterface]() + var size = WorkBufferSize + var tries = 0 + var buffer: seq[byte] + var res: uint32 + var vista = isVista() + + while true: + buffer = newSeq[byte](size) + var addresses = cast[ptr IpAdapterAddressesXp](addr buffer[0]) + res = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nil, + addresses, addr size) + if res == ERROR_SUCCESS: + buffer.setLen(size) + break + elif res == ERROR_BUFFER_OVERFLOW: + discard + else: + break + inc(tries) + if tries >= MaxTries: + break + + if res == ERROR_SUCCESS: + var slider = cast[ptr IpAdapterAddressesXp](addr buffer[0]) + while not isNil(slider): + var iface = NetworkInterface( + ifIndex: cast[int](slider.ifIndex), + ifType: toInterfaceType(slider.ifType), + state: toInterfaceState(slider.operStatus), + name: $slider.adapterName, + desc: $slider.description, + mtu: cast[int](slider.mtu), + maclen: cast[int](slider.physicalAddressLength), + flags: cast[uint64](slider.flags) + ) + copyMem(addr iface.mac[0], addr slider.physicalAddress[0], + len(iface.mac)) + var unicast = slider.unicastAddress + while not isNil(unicast): + var ifaddr = processAddress(slider, unicast, vista) + iface.addresses.add(ifaddr) + unicast = unicast.next + result.add(iface) + slider = slider.next + + sort(result, cmp) + + proc getBestRoute*(address: TransportAddress): Route = + ## Return best applicable OS route, which will be used for connecting to + ## address ``address``. + if isVista(): + let iph = loadLib("iphlpapi.dll") + if iph != nil: + var bestRoute: MibIpForwardRow2 + var empty: TransportAddress + var dest, src: Sockaddr_storage + var luid: uint64 + var destlen: Socklen + address.toSAddr(dest, destlen) + var getBestRoute2 = cast[GETBESTROUTE2](symAddr(iph, "GetBestRoute2")) + var res = getBestRoute2(addr luid, 0'u32, nil, + cast[ptr SOCKADDR_INET](addr dest), + 0'u32, + addr bestRoute, + cast[ptr SOCKADDR_INET](addr src)) + if res == 0: + if src.ss_family == winlean.AF_INET: + fromSAddr(addr src, Socklen(sizeof(Sockaddr_in)), result.source) + elif src.ss_family == winlean.AF_INET6: + fromSAddr(addr src, Socklen(sizeof(Sockaddr_in6)), result.source) + if bestRoute.nextHop.si_family == winlean.AF_INET: + fromSAddr(cast[ptr Sockaddr_storage](addr bestRoute.nextHop), + Socklen(sizeof(Sockaddr_in)), result.gateway) + elif bestRoute.nextHop.si_family == winlean.AF_INET6: + fromSAddr(cast[ptr Sockaddr_storage](addr bestRoute.nextHop), + Socklen(sizeof(Sockaddr_in6)), result.gateway) + if result.gateway.isZero(): + result.gateway = empty + result.dest = address + result.ifIndex = int(bestRoute.interfaceIndex) + result.metric = int(bestRoute.metric) + else: + if address.family == AddressFamily.IPv4: + var bestRoute: MibIpForwardRow + var dest: uint32 + copyMem(addr dest, unsafeAddr address.address_v4[0], 4) + let res = getBestRouteXp(dest, 0'u32, addr bestRoute) + if res == 0: + var interfaces = getInterfaces() + result.dest = address + if bestRoute.dwForwardNextHop != 0'u32: + result.gateway = TransportAddress(family: AddressFamily.IPv4) + copyMem(addr result.gateway.address_v4[0], + addr bestRoute.dwForwardNextHop, 4) + result.metric = int(bestRoute.dwForwardMetric1) + result.ifIndex = int(bestRoute.dwForwardIfIndex) + for item in interfaces: + if item.ifIndex == int(bestRoute.dwForwardIfIndex): + for a in item.addresses: + if a.host.family == AddressFamily.IPv4: + result.source = a.host + break + +else: + {.fatal: "Sorry, your OS is currently not supported!".} diff --git a/tests/testall.nim b/tests/testall.nim index 0b45caf8..3a22080a 100644 --- a/tests/testall.nim +++ b/tests/testall.nim @@ -6,4 +6,4 @@ # Apache License, version 2.0, (LICENSE-APACHEv2) # MIT license (LICENSE-MIT) import testsync, testsoon, testtime, testfut, testsignal, testaddress, - testdatagram, teststream, testserver, testbugs + testdatagram, teststream, testserver, testbugs, testnet diff --git a/tests/testdatagram.nim b/tests/testdatagram.nim index 0b31464e..b2dfec09 100644 --- a/tests/testdatagram.nim +++ b/tests/testdatagram.nim @@ -476,7 +476,7 @@ suite "Datagram Transport test suite": inc(res) transp.close() var dgram1 = newDatagramTransport(clientMark, local = ta1, - flags = {Broadcast}) + flags = {Broadcast}, ttl = 2) await dgram1.sendTo(bta, expectMessage) await wait(dgram1.join(), 5.seconds) result = res diff --git a/tests/testnet.nim b/tests/testnet.nim new file mode 100644 index 00000000..c680f2ec --- /dev/null +++ b/tests/testnet.nim @@ -0,0 +1,264 @@ +# Chronos Test Suite +# (c) Copyright 2018-Present +# Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) +import unittest +import ../chronos + +suite "Network utilities test suite": + test "IPv4 networks test": + var a: TransportAddress + check: + a.isUnspecified() == true + initTAddress("0.0.0.0:0").isUnspecified() == false + + initTAddress("0.0.0.0:0").isZero() == true + initTAddress("1.0.0.0:0").isZero() == false + + initTAddress("127.0.0.0:0").isLoopback() == true + initTAddress("127.255.255.255:0").isLoopback() == true + initTAddress("128.0.0.0:0").isLoopback() == false + initTAddress("126.0.0.0:0").isLoopback() == false + + initTAddress("224.0.0.0:0").isMulticast() == true + initTAddress("230.0.0.0:0").isMulticast() == true + initTAddress("239.255.255.255:0").isMulticast() == true + initTAddress("240.0.0.0:0").isMulticast() == false + initTAddress("223.0.0.0:0").isMulticast() == false + + initTAddress("224.0.0.0:0").isLinkLocalMulticast() == true + initTAddress("224.0.0.255:0").isLinkLocalMulticast() == true + initTAddress("225.0.0.0:0").isLinkLocalMulticast() == false + initTAddress("224.0.1.0:0").isLinkLocalMulticast() == false + + initTAddress("0.0.0.0:0").isAnyLocal() == true + initTAddress("1.0.0.0:0").isAnyLocal() == false + + initTAddress("169.254.0.0:0").isLinkLocal() == true + initTAddress("169.254.255.255:0").isLinkLocal() == true + initTAddress("169.255.0.0:0").isLinkLocal() == false + initTAddress("169.253.0.0:0").isLinkLocal() == false + + initTAddress("10.0.0.0:0").isSiteLocal() == true + initTAddress("10.255.255.255:0").isSiteLocal() == true + initTAddress("11.0.0.0:0").isSiteLocal() == false + initTAddress("9.0.0.0:0").isSiteLocal() == false + initTAddress("172.16.0.0:0").isSiteLocal() == true + initTAddress("172.31.255.255:0").isSiteLocal() == true + initTAddress("172.15.0.0:0").isSiteLocal() == false + initTAddress("172.32.0.0:0").isSiteLocal() == false + initTAddress("192.168.0.0:0").isSiteLocal() == true + initTAddress("192.168.255.255:0").isSiteLocal() == true + initTAddress("192.167.0.0:0").isSiteLocal() == false + initTAddress("192.169.0.0:0").isSiteLocal() == false + + initTAddress("224.0.1.0:0").isGlobalMulticast() == true + initTAddress("238.255.255.255:0").isGlobalMulticast() == true + initTAddress("224.0.0.0:0").isGlobalMulticast() == false + initTAddress("239.0.0.0:0").isGlobalMulticast() == false + + test "IPv6 networks test": + check: + initTAddress("[::]:0").isUnspecified() == false + + initTAddress("[::]:0").isZero() == true + initTAddress("[::1]:0").isZero() == false + + initTAddress("[::1]:0").isLoopback() == true + initTAddress("[::2]:0").isLoopback() == false + + initTAddress("[FF00::]:0").isMulticast() == true + initTAddress("[FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0").isMulticast() == true + initTAddress("[F000::]:0").isMulticast() == false + + initTAddress("[::]:0").isAnyLocal() == true + initTAddress("[::1]:0").isAnyLocal() == false + + initTAddress("[FE80::]:0").isLinkLocal() == true + initTAddress("[FEBF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0").isLinkLocal() == true + initTAddress("[FE7F::]:0").isLinkLocal() == false + initTAddress("[FEC0::]:0").isLinkLocal() == false + + initTAddress("[FEC0::]:0").isSiteLocal() == true + initTAddress("[FEFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0").isSiteLocal() == true + initTAddress("[FEBF::]:0").isSiteLocal() == false + initTAddress("[FF00::]:0").isSiteLocal() == false + + initTAddress("[FF0E::]:0").isGlobalMulticast() == true + initTAddress("[FFFE:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0").isGlobalMulticast() == true + initTAddress("[FF0D::]:0").isGlobalMulticast() == false + initTAddress("[FFFF::]:0").isGlobalMulticast() == false + + test "IP masks test": + check: + $IpMask.init(AddressFamily.IPv4, -1) == "00000000" + $IpMask.init(AddressFamily.IPv4, 0) == "00000000" + $IpMask.init(AddressFamily.IPv4, 4) == "F0000000" + $IpMask.init(AddressFamily.IPv4, 8) == "FF000000" + $IpMask.init(AddressFamily.IPv4, 12) == "FFF00000" + $IpMask.init(AddressFamily.IPv4, 16) == "FFFF0000" + $IpMask.init(AddressFamily.IPv4, 20) == "FFFFF000" + $IpMask.init(AddressFamily.IPv4, 24) == "FFFFFF00" + $IpMask.init(AddressFamily.IPv4, 28) == "FFFFFFF0" + $IpMask.init(AddressFamily.IPv4, 32) == "FFFFFFFF" + $IpMask.init(AddressFamily.IPv4, 33) == "FFFFFFFF" + + IpMask.init(AddressFamily.IPv4, -1) == IpMask.init("00000000") + IpMask.init(AddressFamily.IPv4, 0) == IpMask.init("00000000") + IpMask.init(AddressFamily.IPv4, 4) == IpMask.init("F0000000") + IpMask.init(AddressFamily.IPv4, 8) == IpMask.init("FF000000") + IpMask.init(AddressFamily.IPv4, 12) == IpMask.init("FFF00000") + IpMask.init(AddressFamily.IPv4, 16) == IpMask.init("FFFF0000") + IpMask.init(AddressFamily.IPv4, 20) == IpMask.init("FFFFF000") + IpMask.init(AddressFamily.IPv4, 24) == IpMask.init("FFFFFF00") + IpMask.init(AddressFamily.IPv4, 28) == IpMask.init("FFFFFFF0") + IpMask.init(AddressFamily.IPv4, 32) == IpMask.init("FFFFFFFF") + IpMask.init(AddressFamily.IPv4, 33) == IpMask.init("FFFFFFFF") + + IpMask.init(initTAddress("255.0.0.0:0")) == IpMask.initIp("255.0.0.0") + IpMask.init(initTAddress("255.255.0.0:0")) == IpMask.initIp("255.255.0.0") + IpMask.init(initTAddress("255.255.255.0:0")) == + IpMask.initIp("255.255.255.0") + IpMask.init(initTAddress("255.255.255.255:0")) == + IpMask.initIp("255.255.255.255") + + IpMask.init("00000000").prefix() == 0 + IpMask.init("F0000000").prefix() == 4 + IpMask.init("FF000000").prefix() == 8 + IpMask.init("FFF00000").prefix() == 12 + IpMask.init("FFFF0000").prefix() == 16 + IpMask.init("FFFFF000").prefix() == 20 + IpMask.init("FFFFFF00").prefix() == 24 + IpMask.init("FFFFFFF0").prefix() == 28 + IpMask.init("FFFFFFFF").prefix() == 32 + + IpMask.init("00000000").subnetMask() == initTAddress("0.0.0.0:0") + IpMask.init("F0000000").subnetMask() == initTAddress("240.0.0.0:0") + IpMask.init("FF000000").subnetMask() == initTAddress("255.0.0.0:0") + IpMask.init("FFF00000").subnetMask() == initTAddress("255.240.0.0:0") + IpMask.init("FFFF0000").subnetMask() == initTAddress("255.255.0.0:0") + IpMask.init("FFFFF000").subnetMask() == initTAddress("255.255.240.0:0") + IpMask.init("FFFFFF00").subnetMask() == initTAddress("255.255.255.0:0") + IpMask.init("FFFFFFF0").subnetMask() == initTAddress("255.255.255.240:0") + IpMask.init("FFFFFFFF").subnetMask() == initTAddress("255.255.255.255:0") + + IpMask.init("00000000").ip() == "0.0.0.0" + IpMask.init("F0000000").ip() == "240.0.0.0" + IpMask.init("FF000000").ip() == "255.0.0.0" + IpMask.init("FFF00000").ip() == "255.240.0.0" + IpMask.init("FFFF0000").ip() == "255.255.0.0" + IpMask.init("FFFFF000").ip() == "255.255.240.0" + IpMask.init("FFFFFF00").ip() == "255.255.255.0" + IpMask.init("FFFFFFF0").ip() == "255.255.255.240" + IpMask.init("FFFFFFFF").ip() == "255.255.255.255" + + initTAddress("241.241.241.241:0").mask(IpMask.init("00000000")) == + initTAddress("0.0.0.0:0") + initTAddress("241.241.241.241:0").mask(IpMask.init("F0000000")) == + initTAddress("240.0.0.0:0") + initTAddress("241.241.241.241:0").mask(IpMask.init("FF000000")) == + initTAddress("241.0.0.0:0") + initTAddress("241.241.241.241:0").mask(IpMask.init("FFF00000")) == + initTAddress("241.240.0.0:0") + initTAddress("241.241.241.241:0").mask(IpMask.init("FFFF0000")) == + initTAddress("241.241.0.0:0") + initTAddress("241.241.241.241:0").mask(IpMask.init("FFFFF000")) == + initTAddress("241.241.240.0:0") + initTAddress("241.241.241.241:0").mask(IpMask.init("FFFFFF00")) == + initTAddress("241.241.241.0:0") + initTAddress("241.241.241.241:0").mask(IpMask.init("FFFFFFF0")) == + initTAddress("241.241.241.240:0") + initTAddress("241.241.241.241:0").mask(IpMask.init("FFFFFFFF")) == + initTAddress("241.241.241.241:0") + + test "IP networks test": + check: + IpNet.init(initTAddress("192.168.0.1:0"), 0) == + IpNet.init("192.168.0.1/0.0.0.0") + IpNet.init(initTAddress("192.168.0.1:0"), 4) == + IpNet.init("192.168.0.1/240.0.0.0") + IpNet.init(initTAddress("192.168.0.1:0"), 8) == + IpNet.init("192.168.0.1/255.0.0.0") + IpNet.init(initTAddress("192.168.0.1:0"), 12) == + IpNet.init("192.168.0.1/255.240.0.0") + IpNet.init(initTAddress("192.168.0.1:0"), 16) == + IpNet.init("192.168.0.1/255.255.0.0") + IpNet.init(initTAddress("192.168.0.1:0"), 20) == + IpNet.init("192.168.0.1/255.255.240.0") + IpNet.init(initTAddress("192.168.0.1:0"), 24) == + IpNet.init("192.168.0.1/255.255.255.0") + IpNet.init(initTAddress("192.168.0.1:0"), 28) == + IpNet.init("192.168.0.1/255.255.255.240") + IpNet.init(initTAddress("192.168.0.1:0"), 32) == + IpNet.init("192.168.0.1/255.255.255.255") + + IpNet.init(initTAddress("192.168.0.1:0"), 0) == + IpNet.init("192.168.0.1/0") + IpNet.init(initTAddress("192.168.0.1:0"), 4) == + IpNet.init("192.168.0.1/4") + IpNet.init(initTAddress("192.168.0.1:0"), 8) == + IpNet.init("192.168.0.1/8") + IpNet.init(initTAddress("192.168.0.1:0"), 12) == + IpNet.init("192.168.0.1/12") + IpNet.init(initTAddress("192.168.0.1:0"), 16) == + IpNet.init("192.168.0.1/16") + IpNet.init(initTAddress("192.168.0.1:0"), 20) == + IpNet.init("192.168.0.1/20") + IpNet.init(initTAddress("192.168.0.1:0"), 24) == + IpNet.init("192.168.0.1/24") + IpNet.init(initTAddress("192.168.0.1:0"), 28) == + IpNet.init("192.168.0.1/28") + IpNet.init(initTAddress("192.168.0.1:0"), 32) == + IpNet.init("192.168.0.1/32") + + IpNet.init("192.168.0.1/24").contains(initTAddress("192.168.0.1:0")) == + true + IpNet.init("192.168.0.1/24").contains(initTAddress("192.168.0.128:0")) == + true + IpNet.init("192.168.0.1/24").contains(initTAddress("192.168.0.255:0")) == + true + IpNet.init("192.168.0.1/24").contains(initTAddress("192.168.1.0:0")) == + false + IpNet.init("192.168.0.1/0").contains(initTAddress("1.1.1.1:0")) == + true + IpNet.init("192.168.0.1/32").contains(initTAddress("192.168.0.1:0")) == + true + IpNet.init("192.168.0.1/32").contains(initTAddress("192.168.0.2:0")) == + false + + test "IPv4 <-> IPv6 mapping test": + check: + initTAddress("255.255.255.255:0").toIPv6() == + initTAddress("[::FFFF:FFFF:FFFF]:0") + initTAddress("128.128.128.128:0").toIPv6() == + initTAddress("[::FFFF:8080:8080]:0") + initTAddress("1.1.1.1:0").toIPv6() == initTAddress("[::FFFF:0101:0101]:0") + initTAddress("0.0.0.0:0").toIPv6() == initTAddress("[::FFFF:0000:0000]:0") + initTAddress("[::FFFF:FFFF:FFFF]:0").isV4Mapped() == true + initTAddress("[::FFFF:8080:8080]:0").isV4Mapped() == true + initTAddress("[::FFFF:0101:0101]:0").isV4Mapped() == true + initTAddress("[::FFFF:0000:0000]:0").isV4Mapped() == true + initTAddress("[::FFFF:FFFF:FFFF]:0").toIPv4() == + initTAddress("255.255.255.255:0") + initTAddress("[::FFFF:8080:8080]:0").toIPv4() == + initTAddress("128.128.128.128:0") + initTAddress("[::FFFF:0101:0101]:0").toIPv4() == initTAddress("1.1.1.1:0") + initTAddress("[::FFFF:0000:0000]:0").toIPv4() == initTAddress("0.0.0.0:0") + + test "getInterfaces() test": + var ifaces = getInterfaces() + check: + len(ifaces) > 0 + for item in ifaces: + echo item + + test "getBestRoute() test": + var route = getBestRoute(initTAddress("8.8.8.8:0")) + check: + route.source.isUnspecified() == false + route.dest.isUnspecified() == false + route.ifIndex != 0 + echo route