From 1e743dc415646cb13ffe54ce7fd389008d750ea1 Mon Sep 17 00:00:00 2001 From: Eugene Kabanov Date: Thu, 16 Feb 2023 18:18:05 +0200 Subject: [PATCH] IpNet refactoring (#354) Remove `result` usage. Remove cast[T] usage. Add more helpers for TransportAddress checks. Add isGlobal() helper to check if TransportAddress is globally accessed IP address. Add tests. Add more tests for sub. Add TransportAddress.toHex(). --- chronos/transports/common.nim | 31 +- chronos/transports/ipnet.nim | 1038 ++++++++++++++++++++++----------- tests/testnet.nim | 227 ++++++- 3 files changed, 916 insertions(+), 380 deletions(-) diff --git a/chronos/transports/common.nim b/chronos/transports/common.nim index 3640971..fad913e 100644 --- a/chronos/transports/common.nim +++ b/chronos/transports/common.nim @@ -13,7 +13,7 @@ else: {.push raises: [].} import std/[os, strutils, nativesockets, net] -import stew/base10 +import stew/[base10, endians2, byteutils] import ../asyncloop export net @@ -137,19 +137,16 @@ var proc `==`*(lhs, rhs: TransportAddress): bool = ## Compare two transport addresses ``lhs`` and ``rhs``. Return ``true`` if ## addresses are equal. - if lhs.family != rhs.family: - return false + if (lhs.family != rhs.family): return false case lhs.family of AddressFamily.None: true of AddressFamily.IPv4: - equalMem(unsafeAddr lhs.address_v4[0], - unsafeAddr rhs.address_v4[0], sizeof(lhs.address_v4)) and - (lhs.port == rhs.port) + if lhs.port != rhs.port: return false + lhs.address_v4 == rhs.address_v4 of AddressFamily.IPv6: - equalMem(unsafeAddr lhs.address_v6[0], - unsafeAddr rhs.address_v6[0], sizeof(lhs.address_v6)) and - (lhs.port == rhs.port) + if lhs.port != rhs.port: return false + lhs.address_v6 == rhs.address_v6 of AddressFamily.Unix: equalMem(unsafeAddr lhs.address_un[0], unsafeAddr rhs.address_un[0], sizeof(lhs.address_un)) @@ -195,8 +192,20 @@ proc `$`*(address: TransportAddress): string = $cast[cstring](addr buffer) else: "/" - else: - "Unknown address family: " & $address.family + of AddressFamily.None: + "None" + +proc toHex*(address: TransportAddress): string = + ## Returns hexadecimal representation of ``address`. + case address.family + of AddressFamily.IPv4: + "0x" & address.address_v4.toHex() + of AddressFamily.IPv6: + "0x" & address.address_v6.toHex() + of AddressFamily.Unix: + "0x" & address.address_un.toHex() + of AddressFamily.None: + "None" proc initTAddress*(address: string): TransportAddress {. raises: [Defect, TransportAddressError].} = diff --git a/chronos/transports/ipnet.nim b/chronos/transports/ipnet.nim index af7469e..1d61cb6 100644 --- a/chronos/transports/ipnet.nim +++ b/chronos/transports/ipnet.nim @@ -33,76 +33,63 @@ type 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: - result.mask4 = mask.mask4.toBE() - elif mask.family == AddressFamily.IPv6: - result.mask6[0] = mask.mask6[0].toBE() - result.mask6[1] = mask.mask6[1].toBE() - -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). - - result = IpMask(family: mask.family) - if mask.family == AddressFamily.IPv4: - result.mask4 =mask.mask4.fromBE() - elif mask.family == AddressFamily.IPv6: - result.mask6[0] = mask.mask6[0].fromBE() - result.mask6[1] = mask.mask6[1].fromBE() - 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])) + case m1.family + of AddressFamily.IPv4: + (m1.mask4 == m2.mask4) + of AddressFamily.IPv6: + ((m1.mask6[0] == m2.mask6[0]) and (m1.mask6[1] == m2.mask6[1])) + else: + true + else: + false 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) + case family + of AddressFamily.IPv4: if prefix <= 0: - result.mask4 = 0x00'u32 + IpMask(family: AddressFamily.IPv4, mask4: 0'u32) + elif prefix < 32: + let mask = 0xFFFF_FFFF'u32 shl (32 - prefix) + IpMask(family: AddressFamily.IPv4, mask4: mask.toBE()) 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) + IpMask(family: AddressFamily.IPv4, mask4: 0xFFFF_FFFF'u32) + of AddressFamily.IPv6: if prefix <= 0: - result.mask6[0] = 0x00'u64 - result.mask6[1] = 0x00'u64 + IpMask(family: AddressFamily.IPv6, mask6: [0'u64, 0'u64]) elif prefix >= 128: - result.mask6[0] = 0xFFFF_FFFF_FFFF_FFFF'u64 - result.mask6[1] = 0xFFFF_FFFF_FFFF_FFFF'u64 + IpMask(family: AddressFamily.IPv6, + mask6: [0xFFFF_FFFF_FFFF_FFFF'u64, 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) + let mask = 0xFFFF_FFFF_FFFF_FFFF'u64 shl (128 - prefix) + IpMask(family: AddressFamily.IPv6, + mask6: [0xFFFF_FFFF_FFFF_FFFF'u64, mask.toBE()]) elif prefix == 64: - result.mask6[1] = 0x00'u64 + IpMask(family: AddressFamily.IPv6, + mask6: [0xFFFF_FFFF_FFFF_FFFF'u64, 0'u64]) else: - result.mask6[0] = result.mask6[0] shl (64 - prefix) - result.mask6[1] = 0x00'u64 - result = result.toNetworkOrder() + let mask = 0xFFFF_FFFF_FFFF_FFFF'u64 shl (64 - prefix) + IpMask(family: AddressFamily.IPv6, mask6: [mask.toBE(), 0'u64]) + else: + IpMask(family: family) proc init*(t: typedesc[IpMask], netmask: TransportAddress): IpMask = ## Initialize network mask using address ``netmask``. - if netmask.family == AddressFamily.IPv4: - result = IpMask(family: netmask.family) - result.mask4 = cast[ptr uint32](unsafeAddr netmask.address_v4[0])[] - elif netmask.family == AddressFamily.IPv6: - result = IpMask(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])[] + case netmask.family + of AddressFamily.IPv4: + IpMask(family: AddressFamily.IPv4, + mask4: uint32.fromBytes(netmask.address_v4)) + of AddressFamily.IPv6: + IpMask(family: AddressFamily.IPv6, + mask6: [uint64.fromBytes(netmask.address_v6.toOpenArray(0, 7)), + uint64.fromBytes(netmask.address_v6.toOpenArray(8, 15))]) + else: + IpMask(family: netmask.family) proc initIp*(t: typedesc[IpMask], netmask: string): IpMask = ## Initialize network mask using IPv4 or IPv6 address in text representation @@ -113,9 +100,9 @@ proc initIp*(t: typedesc[IpMask], netmask: string): IpMask = try: var ip = parseIpAddress(netmask) var tip = initTAddress(ip, Port(0)) - result = t.init(tip) + t.init(tip) except ValueError: - discard + IpMask(family: AddressFamily.None) proc init*(t: typedesc[IpMask], netmask: string): IpMask = ## Initialize network mask using hexadecimal string representation @@ -135,18 +122,18 @@ proc init*(t: typedesc[IpMask], netmask: string): IpMask = offset = 2 var res = IpMask(family: AddressFamily.IPv4) var r, v: uint32 - for i in 0..<8: + for i in 0 ..< 8: if netmask[offset + i] in hexNumbers: - v = cast[uint32](ord(netmask[offset + i]) - ord('0')) + v = uint32(ord(netmask[offset + i]) - ord('0')) elif netmask[offset + i] in hexCapitals: - v = cast[uint32](ord(netmask[offset + i]) - ord('A') + 10) + v = uint32(ord(netmask[offset + i]) - ord('A') + 10) elif netmask[offset + i] in hexLowers: - v = cast[uint32](ord(netmask[offset + i]) - ord('a') + 10) + v = uint32(ord(netmask[offset + i]) - ord('a') + 10) else: return r = (r shl 4) or v res.mask4 = r.toBE() - result = res + res elif length == 32 or length == (2 + 32): ## IPv6 mask var offset = 0 @@ -155,19 +142,21 @@ proc init*(t: typedesc[IpMask], netmask: string): IpMask = var res = IpMask(family: AddressFamily.IPv6) for i in 0..1: var r, v: uint64 - for i in 0..<16: + for i in 0 ..< 16: if netmask[offset + i] in hexNumbers: - v = cast[uint64](ord(netmask[offset + i]) - ord('0')) + v = uint64(ord(netmask[offset + i]) - ord('0')) elif netmask[offset + i] in hexCapitals: - v = cast[uint64](ord(netmask[offset + i]) - ord('A') + 10) + v = uint64(ord(netmask[offset + i]) - ord('A') + 10) elif netmask[offset + i] in hexLowers: - v = cast[uint64](ord(netmask[offset + i]) - ord('a') + 10) + v = uint64(ord(netmask[offset + i]) - ord('a') + 10) else: return r = (r shl 4) or v offset += 16 res.mask6[i] = r.toBE() - result = res + res + else: + IpMask(family: AddressFamily.None) proc toIPv6*(address: TransportAddress): TransportAddress = ## Map IPv4 ``address`` to IPv6 address. @@ -176,26 +165,33 @@ proc toIPv6*(address: TransportAddress): TransportAddress = ## <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.port = address.port - 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 + case address.family + of AddressFamily.IPv4: + var address6: array[16, uint8] + address6[10] = 0xFF'u8 + address6[11] = 0xFF'u8 + let ip4 = uint32.fromBytes(address.address_v4) + address6[12 .. 15] = ip4.toBytes() + TransportAddress(family: AddressFamily.IPv6, port: address.port, + address_v6: address6) + of AddressFamily.IPv6: + address + else: + raiseAssert "Invalid address family type" 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) + case address.family + of AddressFamily.IPv6: + let data0 = uint64.fromBytes(address.address_v6.toOpenArray(0, 7)) + let data1 = uint16.fromBytes(address.address_v6.toOpenArray(8, 9)) + let data2 = uint16.fromBytes(address.address_v6.toOpenArray(10, 11)) + (data0 == 0x00'u64) and (data1 == 0x00'u16) and (data2 == 0xFFFF'u16) + else: + false proc toIPv4*(address: TransportAddress): TransportAddress = ## Get IPv4 from (IPv4 to IPv6) mapped address. @@ -204,14 +200,18 @@ proc toIPv4*(address: TransportAddress): TransportAddress = ## ## If ``address`` is not IPv4 to IPv6 mapped address, then result family will ## be set to AddressFamily.None. - if address.family == AddressFamily.IPv6: + case address.family + of AddressFamily.IPv4: + address + of AddressFamily.IPv6: if isV4Mapped(address): - result = TransportAddress(family: AddressFamily.IPv4) - result.port = address.port - 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 + let data = uint32.fromBytes(address.address_v6.toOpenArray(12, 15)) + TransportAddress(family: AddressFamily.IPv4, port: address.port, + address_v4: data.toBytes()) + else: + TransportAddress(family: AddressFamily.None) + else: + TransportAddress(family: AddressFamily.None) proc mask*(a: TransportAddress, m: IpMask): TransportAddress = ## Apply IP mask ``m`` to address ``a`` and return result address. @@ -231,146 +231,160 @@ proc mask*(a: TransportAddress, m: IpMask): TransportAddress = ## IPv6 address. ## ## In all other cases returned address will have ``AddressFamily.None``. - if a.family == AddressFamily.IPv4 and m.family == AddressFamily.IPv6: + 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: + let + mask = uint32((m.mask6[1] shr 32) and 0xFFFF_FFFF'u64) + data = uint32.fromBytes(a.address_v4) + TransportAddress(family: AddressFamily.IPv4, port: a.port, + address_v4: (data and mask).toBytes()) + else: + TransportAddress(family: AddressFamily.None) + 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 + if ip.family != AddressFamily.IPv4: + return TransportAddress(family: AddressFamily.None) + let data = uint32.fromBytes(ip.address_v4) + ip.address_v4[0 .. 3] = (data and m.mask4).toBytes() + var res = ip.toIPv6() + res.port = a.port + res 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 + let data = uint32.fromBytes(a.address_v4) + TransportAddress(family: AddressFamily.IPv4, port: a.port, + address_v4: (data and m.mask4).toBytes()) 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 + var address6: array[16, uint8] + let + data0 = uint64.fromBytes(a.address_v6.toOpenArray(0, 7)) + data1 = uint64.fromBytes(a.address_v6.toOpenArray(8, 15)) + address6[0 .. 7] = (data0 and m.mask6[0]).toBytes() + address6[8 .. 15] = (data1 and m.mask6[1]).toBytes() + TransportAddress(family: AddressFamily.IPv6, port: a.port, + address_v6: address6) + else: + TransportAddress(family: AddressFamily.None) 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 + case mask.family + of AddressFamily.IPv4: + var + res = 0 + n = mask.mask4.fromBE() while n != 0: - if (n and 0x8000_0000'u32) == 0'u32: - result = -1 - return + if (n and 0x8000_0000'u32) == 0'u32: return -1 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: + inc(res) + res + of AddressFamily.IPv6: + let mask6 = [mask.mask6[0].fromBE(), mask.mask6[1].fromBE()] + var res = 0 + if mask6[0] == 0xFFFF_FFFF_FFFF_FFFF'u64: + res += 64 + if mask6[1] == 0xFFFF_FFFF_FFFF_FFFF'u64: + res + 64 else: - var n = hmask.mask6[1] + var n = mask6[1] while n != 0: - if (n and 0x8000_0000_0000_0000'u64) == 0'u64: - result = -1 - return + if (n and 0x8000_0000_0000_0000'u64) == 0'u64: return -1 n = n shl 1 - inc(result) + inc(res) + res else: - var n = hmask.mask6[0] + var n = mask6[0] while n != 0: - if (n and 0x8000_0000_0000_0000'u64) == 0'u64: - result = -1 - return + if (n and 0x8000_0000_0000_0000'u64) == 0'u64: return -1 n = n shl 1 - inc(result) - if hmask.mask6[1] != 0x00'u64: - result = -1 + inc(res) + if mask6[1] != 0x00'u64: return -1 + res + else: + -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] + case mask.family + of AddressFamily.IPv4: + TransportAddress(family: AddressFamily.IPv4, + address_v4: mask.mask4.toBytes()) + of AddressFamily.IPv6: + var address6: array[16, uint8] + address6[0 .. 7] = mask.mask6[0].toBytes() + address6[8 .. 15] = mask.mask6[1].toBytes() + TransportAddress(family: AddressFamily.IPv6, address_v6: address6) + else: + TransportAddress(family: mask.family) 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: "" + case mask.family + of AddressFamily.IPv4: + var res = if include0x: "0x" else: "" var n = 32 - var m = host.mask4 + var m = mask.mask4.fromBE() while n > 0: n -= 4 var c = int((m shr n) and 0x0F) if c < 10: - result.add(chr(ord('0') + c)) + res.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: + res.add(chr(ord('A') + (c - 10))) + res + of AddressFamily.IPv6: + let mask6 = [mask.mask6[0].fromBE(), mask.mask6[1].fromBE()] + var res = if include0x: "0x" else: "" + for i in 0 .. 1: var n = 64 - var m = host.mask6[i] + var m = mask6[i] while n > 0: n -= 4 var c = int((m shr n) and 0x0F) if c < 10: - result.add(chr(ord('0') + c)) + res.add(chr(ord('0') + c)) else: - result.add(chr(ord('A') + (c - 10))) + res.add(chr(ord('A') + (c - 10))) + res else: - return "Unknown mask family: " & $host.family + "Unknown mask family: " & $mask.family proc ip*(mask: IpMask): string {.raises: [Defect, ValueError].} = ## 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 + case mask.family + of AddressFamily.IPv4: + var address4: array[4, uint8] + copyMem(addr address4[0], unsafeAddr mask.mask4, sizeof(uint32)) + $IpAddress(family: IpAddressFamily.IPv4, address_v4: address4) + of AddressFamily.Ipv6: + var address6: array[16, uint8] + copyMem(addr address6[0], unsafeAddr mask.mask6[0], 16) + $IpAddress(family: IpAddressFamily.IPv6, address_v6: address6) else: - raise newException(ValueError, "Invalid mask") + raise newException(ValueError, "Invalid mask family type") 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 + IpNet(mask: IpMask.init(host.family, prefix), 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 + doAssert(host.family == mask.family) + IpNet(mask: IpMask.init(mask), 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 + IpNet(mask: mask, host: host) proc init*(t: typedesc[IpNet], network: string): IpNet {. raises: [Defect, TransportAddressError].} = @@ -426,176 +440,283 @@ proc init*(t: typedesc[IpNet], network: string): IpNet {. 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) + if n1.host.family != n2.host.family: + return false + case n1.host.family + of AddressFamily.IPv4: + (n1.host.address_v4 == n2.host.address_v4) and (n1.mask == n2.mask) + of AddressFamily.IPv6: + (n1.host.address_v6 == n2.host.address_v6) and (n1.mask == n2.mask) + else: + false 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) + if net.host.family != address.family: + return false + var host1 = mask(address, net.mask) + var host2 = mask(net.host, net.mask) + host2.port = host1.port + 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)) + case net.host.family + of AddressFamily.IPv4: + let + host = uint32.fromBytes(net.host.address_v4) + mask = net.mask.mask4 + TransportAddress(family: AddressFamily.IPv4, + address_v4: (host or (not(mask))).toBytes()) + of AddressFamily.IPv6: + var address6: array[16, uint8] + let + host0 = uint64.fromBytes(net.host.address_v6.toOpenArray(0, 7)) + host1 = uint64.fromBytes(net.host.address_v6.toOpenArray(8, 15)) + data0 = net.mask.mask6[0] + data1 = net.mask.mask6[1] + address6[0 .. 7] = (host0 or (not(data0))).toBytes() + address6[8 .. 15] = (host1 or (not(data1))).toBytes() + TransportAddress(family: AddressFamily.IPv6, address_v6: address6) + else: + TransportAddress(family: AddressFamily.None) 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 + subnetMask(net.mask) 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) + 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 + doAssert(address1.family == address2.family) + case address1.family + of AddressFamily.IPv4: + let + data1 = uint32.fromBytes(address1.address_v4) + data2 = uint32.fromBytes(address2.address_v4) + TransportAddress(family: AddressFamily.IPv4, + address_v4: (data1 and data2).toBytes()) + of AddressFamily.IPv6: + var address6: array[16, uint8] + let + data1 = uint64.fromBytes(address1.address_v6.toOpenArray(0, 7)) + data2 = uint64.fromBytes(address1.address_v6.toOpenArray(8, 15)) + data3 = uint64.fromBytes(address2.address_v6.toOpenArray(0, 7)) + data4 = uint64.fromBytes(address2.address_v6.toOpenArray(8, 15)) + address6[0 .. 7] = (data1 and data3).toBytes() + address6[8 .. 15] = (data2 and data4).toBytes() + TransportAddress(family: AddressFamily.IPv6, address_v6: address6) + else: + raiseAssert "Invalid address family type" 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 + doAssert(address1.family == address2.family) + case address1.family + of AddressFamily.IPv4: + let + data1 = uint32.fromBytes(address1.address_v4) + data2 = uint32.fromBytes(address2.address_v4) + TransportAddress(family: AddressFamily.IPv4, + address_v4: (data1 or data2).toBytes()) + of AddressFamily.IPv6: + var address6: array[16, uint8] + let + data1 = uint64.fromBytes(address1.address_v6.toOpenArray(0, 7)) + data2 = uint64.fromBytes(address1.address_v6.toOpenArray(8, 15)) + data3 = uint64.fromBytes(address2.address_v6.toOpenArray(0, 7)) + data4 = uint64.fromBytes(address2.address_v6.toOpenArray(8, 15)) + address6[0 .. 7] = (data1 or data3).toBytes() + address6[8 .. 15] = (data2 or data4).toBytes() + TransportAddress(family: AddressFamily.IPv6, address_v6: address6) + else: + raiseAssert "Invalid address family type" 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) + case address.family + of AddressFamily.IPv4: + let data = not(uint32.fromBytes(address.address_v4)) + TransportAddress(family: AddressFamily.IPv4, address_v4: data.toBytes()) + of AddressFamily.IPv6: + var address6: array[16, uint8] + let + data1 = not(uint64.fromBytes(address.address_v6.toOpenArray(0, 7))) + data2 = not(uint64.fromBytes(address.address_v6.toOpenArray(8, 15))) + address6[0 .. 7] = data1.toBytes() + address6[8 .. 15] = data2.toBytes() + TransportAddress(family: AddressFamily.IPv6, address_v6: address6) + else: + address -proc `+`*(address: TransportAddress, v: uint): TransportAddress = - ## Add to IPv4/IPv6 transport ``address`` unsigned integer ``v``. - result = TransportAddress(family: address.family, port: address.port) - if address.family == AddressFamily.IPv4: - var a = uint64(uint32.fromBytesBE(address.address_v4)) - a = a + v - result.address_v4[0..<4] = uint32(a).toBytesBE() - elif address.family == AddressFamily.IPv6: - var a1 = uint64.fromBytesBE(address.address_v6.toOpenArray(0, 7)) - var a2 = uint64.fromBytesBE(address.address_v6.toOpenArray(8, 15)) +proc `+`*(address: TransportAddress, v: int|uint): TransportAddress = + ## Add to IPv4/IPv6 transport ``address`` integer ``v``. + if v == 0: return address + case address.family + of AddressFamily.IPv4: + let + av = uint32.fromBytesBE(address.address_v4) + address4 = + when v is int: + if v <= 0: + # Case when v == 0 is already covered. + let v32 = uint32(uint64(not(v) + 1) and 0xFFFF_FFFF'u64) + (av - v32).toBytesBE() + else: + let v32 = uint32(uint64(v) and 0xFFFF_FFFF'u64) + (av + v32).toBytesBE() + else: + let v32 = uint32(uint64(v) and 0xFFFF_FFFF'u64) + (av + v32).toBytesBE() + TransportAddress(family: AddressFamily.IPv4, port: address.port, + address_v4: address4) + of AddressFamily.IPv6: + let a2 = uint64.fromBytesBE(address.address_v6.toOpenArray(8, 15)) + var + a1 = uint64.fromBytesBE(address.address_v6.toOpenArray(0, 7)) + address6: array[16, uint8] + when v is int: + if v <= 0: + # Case when v == 0 is already covered + let a3 = a2 - uint64(not(int64(v)) + 1) + if a3 > a2: a1 = a1 - 1'u64 + address6[0 .. 7] = a1.toBytesBE() + address6[8 .. 15] = a3.toBytesBE() + else: + let a3 = a2 + uint64(v) + if a3 < a2: a1 = a1 + 1'u64 + address6[0 .. 7] = a1.toBytesBE() + address6[8 .. 15] = a3.toBytesBE() + else: + # v is unsigned so it is always bigger than zero. + let a3 = a2 + uint64(v) + if a3 < a2: a1 = a1 + 1'u64 + address6[0 .. 7] = a1.toBytesBE() + address6[8 .. 15] = a3.toBytesBE() - var a3 = a2 + v - if a3 < a2: - ## Overflow - a1 = a1 + 1 - cast[ptr uint64](addr result.address_v6[0])[] = a1.toBE() - cast[ptr uint64](addr result.address_v6[8])[] = a3.toBE() + TransportAddress(family: AddressFamily.IPv6, port: address.port, + address_v6: address6) + else: + address -proc inc*(address: var TransportAddress, v: uint = 1'u) = - ## Increment IPv4/IPv6 transport ``address`` by unsigned integer ``v``. +proc `-`*(address: TransportAddress, v: int|uint): TransportAddress = + ## Sub from IPv4/IPv6 transport ``address`` integer ``v``. + if v == 0: return address + case address.family + of AddressFamily.IPv4: + let + av = uint32.fromBytesBE(address.address_v4) + address4 = + when v is int: + if v <= 0: + # Case when v == 0 is already covered. + let v32 = uint32(uint64(not(v) + 1) and 0xFFFF_FFFF'u64) + (av + v32).toBytesBE() + else: + let v32 = uint32(uint64(v) and 0xFFFF_FFFF'u64) + (av - v32).toBytesBE() + else: + let v32 = uint32(uint64(v) and 0xFFFF_FFFF'u64) + (av - v32).toBytesBE() + TransportAddress(family: AddressFamily.IPv4, port: address.port, + address_v4: address4) + of AddressFamily.IPv6: + let a2 = uint64.fromBytesBE(address.address_v6.toOpenArray(8, 15)) + var + a1 = uint64.fromBytesBE(address.address_v6.toOpenArray(0, 7)) + address6: array[16, uint8] + when v is int: + if v <= 0: + # Case when v == 0 is already covered + let a3 = a2 + uint64(not(int64(v)) + 1) + if a3 < a2: a1 = a1 + 1'u64 + address6[0 .. 7] = a1.toBytesBE() + address6[8 .. 15] = a3.toBytesBE() + else: + let a3 = a2 - uint64(v) + if a3 > a2: a1 = a1 - 1'u64 + address6[0 .. 7] = a1.toBytesBE() + address6[8 .. 15] = a3.toBytesBE() + else: + # v is unsigned so it is always bigger than zero. + let a3 = a2 - uint64(v) + if a3 > a2: a1 = a1 - 1'u64 + address6[0 .. 7] = a1.toBytesBE() + address6[8 .. 15] = a3.toBytesBE() + + TransportAddress(family: AddressFamily.IPv6, port: address.port, + address_v6: address6) + else: + address + +proc inc*(address: var TransportAddress, v: int = 1) = + ## Increment IPv4/IPv6 transport ``address`` by integer ``v``. address = address + v +proc dec*(address: var TransportAddress, v: int = 1) = + ## Decrement IPv4/IPv6 transport ``address`` by 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: - try: - result.add(net.mask.ip()) - except ValueError as exc: - result.add(exc.msg) + var res = + case net.host.family + of AddressFamily.IPv4: + $IpAddress(family: IpAddressFamily.IPv4, address_v4: net.host.address_v4) + of AddressFamily.IPv6: + $IpAddress(family: IpAddressFamily.IPv6, address_v6: net.host.address_v6) 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: - try: - result.add(net.mask.ip()) - except ValueError as exc: - result.add(exc.msg) - else: - result.add($prefix) + return "Invalid network address family" -proc isUnspecified*(address: TransportAddress): bool {.inline.} = - ## Returns ``true`` if ``address`` is not specified yet, e.g. its ``family`` + res.add("/") + let prefix = net.mask.prefix() + if prefix == -1: + try: + res.add(net.mask.ip()) + except ValueError: + return "Invalid network mask address" + else: + res.add($prefix) + res + +template a4(): untyped {.dirty.} = + address.address_v4 +template a6(): untyped {.dirty.} = + address.address_v6 + +proc isNone*(address: TransportAddress): bool {.inline.} = + ## Returns ``true`` if ``address`` is not initialized yet, e.g. its ``family`` ## field is not set or equal to ``AddressFamily.None``. - if address.family == AddressFamily.None: - result = true + address.family == AddressFamily.None 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 + case address.family + of AddressFamily.IPv4: + uint32.fromBytes(a4()) == 0'u32 + of AddressFamily.IPv6: + (uint64.fromBytes(a6.toOpenArray(0, 7)) == 0'u64) and + (uint64.fromBytes(a6.toOpenArray(8, 15)) == 0'u64) + of AddressFamily.Unix: + len($cast[cstring](unsafeAddr address.address_un[0])) == 0 + else: + false + +proc isUnspecified*(address: TransportAddress): bool {.inline.} = + ## Alias for isZero(). + isZero(address) proc isMulticast*(address: TransportAddress): bool = ## Returns ``true`` if ``address`` is a multicast address. @@ -603,30 +724,78 @@ proc isMulticast*(address: TransportAddress): bool = ## ``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) + case address.family + of AddressFamily.IPv4: + (a4[0] and 0xF0'u8) == 0xE0'u8 + of AddressFamily.IPv6: + a6[0] == 0xFF'u8 + else: + false + +proc isUnicast*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is unicast, as defined by [IETF RFC 4291]. + ## Any address that is not a IPv6 multicast address `FF00::/8` is unicast. + case address.family + of AddressFamily.IPv4: + not(isZero(address) and not isMulticast(address)) + of AddressFamily.IPv6: + not(isMulticast(address)) + else: + false 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) + case address.family + of AddressFamily.IPv4: + false + of AddressFamily.IPv6: + (a6[0] == 0xFF'u8) and ((a6[1] and 0x0F'u8) == 0x01'u8) + else: + false 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) + case address.family + of AddressFamily.IPv4: + (a4[0] == 224'u8) and (a4[1] == 0'u8) and (a4[2] == 0'u8) + of AddressFamily.IPv6: + (a6[0] == 0xFF'u8) and ((a6[1] and 0x0F'u8) == 0x02'u8) + else: + false + +proc isUniqueLocal*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is a unique local IPv6 unicast address. + ## + ## ``IPv6``: FC00::/7 + case address.family + of AddressFamily.IPv4: + false + of AddressFamily.IPv6: + (a6[0] and 0xFE'u8) == 0xFC'u8 + else: + false + +proc isUnicastLinkLocal*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is a IPv6 unicast address with link-local + ## scope. + ## + ## NOTE: While [RFC 4291 section 2.5.3] mentions about the [loopback address] + ## `::1` that "it is treated as having Link-Local scope", this does not mean + ## that the loopback address actually has link-local scope and procedure + ## will return `false` on it. + ## + ## ``IPv6``: FE80::/10 + case address.family + of AddressFamily.IPv4: + false + of AddressFamily.IPv6: + ((a6[0] and 0xFF'u8) == 0xFE'u8) and ((a6[1] and 0xC0'u8) == 0x80'u8) + else: + false proc isLoopback*(address: TransportAddress): bool = ## Returns ``true`` if ``address`` is loopback address. @@ -634,13 +803,14 @@ proc isLoopback*(address: TransportAddress): bool = ## ``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 int(address.address_v6[i]) - result = (test == 0) and (address.address_v6[15] == 1'u8) + case address.family + of AddressFamily.IPv4: + a4[0] == 127'u8 + of AddressFamily.IPv6: + (uint64.fromBytes(a6.toOpenArray(0, 7)) == 0x00'u64) and + (uint64.fromBytesBE(a6.toOpenArray(8, 15)) == 0x01'u64) + else: + false proc isAnyLocal*(address: TransportAddress): bool = ## Returns ``true`` if ``address`` is a wildcard address. @@ -648,15 +818,14 @@ proc isAnyLocal*(address: TransportAddress): bool = ## ``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) + case address.family + of AddressFamily.IPv4: + uint32.fromBytes(a4) == 0'u32 + of AddressFamily.IPv6: + (uint64.fromBytes(a6.toOpenArray(0, 7)) == 0x00'u64) and + (uint64.fromBytes(a6.toOpenArray(8, 15)) == 0x00'u64) + else: + false proc isLinkLocal*(address: TransportAddress): bool = ## Returns ``true`` if ``address`` is link local address. @@ -664,15 +833,16 @@ proc isLinkLocal*(address: TransportAddress): bool = ## ``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) + case address.family + of AddressFamily.IPv4: + (a4[0] == 169'u8) and (a4[1] == 254'u8) + of AddressFamily.IPv6: + (a6[0] == 0xFE'u8) and ((a6[1] and 0xC0'u8) == 0x80'u8) + else: + false proc isLinkLocalUnicast*(address: TransportAddress): bool {.inline.} = - result = isLinkLocal(address) + isLinkLocal(address) proc isSiteLocal*(address: TransportAddress): bool = ## Returns ``true`` if ``address`` is site local address. @@ -681,15 +851,18 @@ proc isSiteLocal*(address: TransportAddress): bool = ## 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) + case address.family + of AddressFamily.IPv4: + (a4[0] == 10'u8) or ((a4[0] == 172'u8) and ((a4[1] and 0xF0) == 16)) or + ((a4[0] == 192'u8) and ((a4[1] == 168'u8))) + of AddressFamily.IPv6: + (a6[0] == 0xFE'u8) and ((a6[1] and 0xC0'u8) == 0xC0'u8) + else: + false + +proc isPrivate*(address: TransportAddress): bool = + ## Alias for ``isSiteLocal()``. + isSiteLocal(address) proc isGlobalMulticast*(address: TransportAddress): bool = ## Returns ``true`` if the multicast address has global scope. @@ -697,14 +870,177 @@ proc isGlobalMulticast*(address: TransportAddress): bool = ## ``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) + case address.family + of AddressFamily.IPv4: + (a4[0] >= 224'u8) and (a4[0] <= 238'u8) and + not((a4[0] == 224'u8) and (a4[1] == 0'u8) and (a4[2] == 0'u8)) + of AddressFamily.IPv6: + (a6[0] == 0xFF'u8) and ((a6[1] and 0x0F'u8) == 0x0E'u8) + else: + false + +proc isShared*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is part of the Shared Address Space + ## defined in [IETF RFC 6598] + ## + ## ``IPv4``: 100.64.0.0/10 + case address.family + of AddressFamily.IPv4: + (a4[0] == 100'u8) and ((a4[1] and 0xC0'u8) == 0x40'u8) + of AddressFamily.IPv6: + false + else: + false + +proc isBroadcast*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is a broadcast address. + ## + ## ``IPv4``: 255.255.255.255 + case address.family + of AddressFamily.IPv4: + uint32.fromBytes(a4) == 0xFFFF_FFFF'u32 + of AddressFamily.IPv6: + false + else: + false + +proc isBenchmarking*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is part of the `198.18.0.0/15` range, + ## which is reserved for network devices benchmarking. This range is defined + ## in [IETF RFC 2544] as `198.18.0.0` through `198.19.255.255` but + ## [errata 423] corrects it to `198.18.0.0/15`. + ## + ## ``IPv4``: 198.18.0.0/15 + ## + ## ``IPv6``: 2001:2::/48 + case address.family + of AddressFamily.IPv4: + (a4[0] == 198'u8) and ((a4[1] and 0xFE'u8) == 18'u8) + of AddressFamily.IPv6: + (uint16.fromBytesBE(a6.toOpenArray(0, 1)) == 0x2001'u16) and + (uint16.fromBytesBE(a6.toOpenArray(2, 3)) == 0x02'u16) and + (uint16.fromBytes(a6.toOpenArray(4, 5)) == 0x00'u16) + else: + false + +proc isDocumentation*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is in a range designated for documentation. + ## + ## ``IPv4``: 192.0.2.0/24 (TEST-NET-1) + ## 198.51.100.0/24 (TEST-NET-2) + ## 203.0.113.0/24 (TEST-NET-3) + ## + ## ``IPv6``: 2001:DB8::/32 + case address.family + of AddressFamily.IPv4: + ((a4[0] == 192'u8) and (a4[1] == 0'u8) and (a4[2] == 2'u8)) or + ((a4[0] == 198'u8) and (a4[1] == 51'u8) and (a4[2] == 100'u8)) or + ((a4[0] == 203'u8) and (a4[1] == 0'u8) and (a4[2] == 113'u8)) + of AddressFamily.IPv6: + (uint16.fromBytesBE(a6.toOpenArray(0, 1)) == 0x2001'u16) and + (uint16.fromBytesBE(a6.toOpenArray(2, 3)) == 0xDB8'u16) + else: + false + +proc isReserved*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` is reserved by IANA for future use. + ## [IETF RFC 1112] defines the block of reserved addresses as `240.0.0.0/4`. + ## + ## NOTE: As IANA assigns new addresses, this procedure will be updated. This + ## may result in non-reserved addresses being treated as reserved in code + ## that relies on an outdated version of this procedure. + ## + ## ``IPv4``: 240.0.0.0/4 + case address.family + of AddressFamily.IPv4: + (a4[0] and 240'u8) == 240'u8 + of AddressFamily.IPv6: + false + else: + false + +proc isGlobal*(address: TransportAddress): bool = + ## Returns ``true`` if ``address`` appears to be globally reachable as + ## specified by the [IANA IPv4 Special-Purpose Address Registry]. + case address.family + of AddressFamily.IPv4: + not( + (a4[0] == 0) or + address.isPrivate() or + address.isShared() or + address.isLoopback() or + address.isLinkLocal() or + # address reserver for future protocols `192.0.0.0/24`. + ((a4[0] == 192'u8) and (a4[1] == 0'u8) and (a4[2] == 0'u8)) or + address.isDocumentation() or + address.isBenchmarking() or + address.isReserved() or + address.isBroadcast() + ) + of AddressFamily.IPv6: + not( + address.isUnspecified() or + address.isLoopback() or + ( + # IPv4-Mapped `::FFFF:0:0/96` + (uint64.fromBytes(a6.toOpenArray(0, 7)) == 0x00'u64) and + (uint16.fromBytes(a6.toOpenArray(8, 9)) == 0x00'u16) and + (uint16.fromBytes(a6.toOpenArray(10, 11)) == 0xFFFF'u16) + ) or + ( + # IPv4-IPv6 Translation `64:FF9B:1::/48` + (uint16.fromBytesBE(a6.toOpenArray(0, 1)) == 0x64'u16) and + (uint16.fromBytesBE(a6.toOpenArray(2, 3)) == 0xFF9B'u16) and + (uint16.fromBytesBE(a6.toOpenArray(4, 5)) == 0x01'u16) + ) or + ( + # Discard-Only Address Block `100::/64` + (uint16.fromBytesBE(a6.toOpenArray(0, 1)) == 0x100'u16) and + (uint32.fromBytes(a6.toOpenArray(2, 5)) == 0x00'u32) and + (uint16.fromBytes(a6.toOpenArray(6, 7)) == 0x00'u16) + ) or + ( + # IETF Protocol Assignments `2001::/23` + (uint16.fromBytesBE(a6.toOpenArray(0, 1)) == 0x2001'u16) and + (uint16.fromBytesBE(a6.toOpenArray(2, 3)) < 0x200'u16) and + not( + ( + # Port Control Protocol Anycast `2001:1::1` + (uint32.fromBytesBE(a6.toOpenArray(0, 3)) == 0x20010001'u32) and + (uint32.fromBytes(a6.toOpenArray(4, 7)) == 0x00'u32) and + (uint32.fromBytes(a6.toOpenArray(8, 11)) == 0x00'u32) and + (uint32.fromBytesBE(a6.toOpenArray(12, 15)) == 0x01'u32) + ) or + ( + # Traversal Using Relays around NAT Anycast `2001:1::2` + (uint32.fromBytesBE(a6.toOpenArray(0, 3)) == 0x20010001'u32) and + (uint32.fromBytes(a6.toOpenArray(4, 7)) == 0x00'u32) and + (uint32.fromBytes(a6.toOpenArray(8, 11)) == 0x00'u32) and + (uint32.fromBytesBE(a6.toOpenArray(12, 15)) == 0x02'u32) + ) or + ( + # AMT `2001:3::/32` + (uint16.fromBytesBE(a6.toOpenArray(0, 1)) == 0x2001'u16) and + (uint16.fromBytesBE(a6.toOpenArray(2, 3)) == 0x03'u16) + ) or + ( + # AS112-v6 `2001:4:112::/48` + (uint16.fromBytesBE(a6.toOpenArray(0, 1)) == 0x2001'u16) and + (uint16.fromBytesBE(a6.toOpenArray(2, 3)) == 0x04'u16) and + (uint16.fromBytesBE(a6.toOpenArray(4, 5)) == 0x112'u16) and + (uint16.fromBytes(a6.toOpenArray(6, 7)) == 0x00'u16) + ) or + ( + # ORCHIDv2 `2001:20::/28` + (uint16.fromBytesBE(a6.toOpenArray(0, 1)) == 0x2001'u16) and + (uint16.fromBytesBE(a6.toOpenArray(2, 3)) >= 0x20'u16) and + (uint16.fromBytesBE(a6.toOpenArray(2, 3)) <= 0x2F'u16) + ) + ) + ) or + address.isDocumentation() or + address.isUniqueLocal() or + address.isUnicastLinkLocal() + ) + else: + false diff --git a/tests/testnet.nim b/tests/testnet.nim index 31c3d66..ff78429 100644 --- a/tests/testnet.nim +++ b/tests/testnet.nim @@ -145,11 +145,13 @@ suite "Network utilities test suite": test "IPv4 networks test": var a: TransportAddress check: - a.isUnspecified() == true - initTAddress("0.0.0.0:0").isUnspecified() == false + a.isNone() == true + initTAddress("0.0.0.0:0").isUnspecified() == true + initTAddress("0.0.0.0:0").isNone() == false initTAddress("0.0.0.0:0").isZero() == true initTAddress("1.0.0.0:0").isZero() == false + initTAddress("1.0.0.0:0").isUnspecified() == false initTAddress("127.0.0.0:0").isLoopback() == true initTAddress("127.255.255.255:0").isLoopback() == true @@ -193,38 +195,196 @@ suite "Network utilities test suite": initTAddress("224.0.0.0:0").isGlobalMulticast() == false initTAddress("239.0.0.0:0").isGlobalMulticast() == false + initTAddress("239.255.255.255:0").isReserved() == false + initTAddress("240.0.0.0:0").isReserved() == true + initTAddress("250.0.0.0:0").isReserved() == true + initTAddress("255.254.254.254:0").isReserved() == true + + initTAddress("198.17.255.255:0").isBenchmarking() == false + initTAddress("198.18.0.0:0").isBenchmarking() == true + initTAddress("198.18.0.1:0").isBenchmarking() == true + initTAddress("198.19.0.1:0").isBenchmarking() == true + initTAddress("198.19.255.255:0").isBenchmarking() == true + initTAddress("198.20.0.0:0").isBenchmarking() == false + + initTAddress("192.0.1.255:0").isDocumentation() == false + initTAddress("192.0.2.0:0").isDocumentation() == true + initTAddress("192.0.2.255:0").isDocumentation() == true + initTAddress("192.0.3.0:0").isDocumentation() == false + + initTAddress("198.51.99.255:0").isDocumentation() == false + initTAddress("198.51.100.0:0").isDocumentation() == true + initTAddress("198.51.100.255:0").isDocumentation() == true + initTAddress("198.51.101.0:0").isDocumentation() == false + + initTAddress("203.0.112.255:0").isDocumentation() == false + initTAddress("203.0.113.0:0").isDocumentation() == true + initTAddress("203.0.113.255:0").isDocumentation() == true + initTAddress("203.0.114.0:0").isDocumentation() == false + + initTAddress("0.0.0.0:0").isBroadcast() == false + initTAddress("127.0.0.255:0").isBroadcast() == false + initTAddress("127.0.255.255:0").isBroadcast() == false + initTAddress("127.255.255.255:0").isBroadcast() == false + initTAddress("255.255.255.255:0").isBroadcast() == true + + initTAddress("100.63.255.255:0").isShared() == false + initTAddress("100.64.0.0:0").isShared() == true + initTAddress("100.64.0.1:0").isShared() == true + initTAddress("100.127.255.255:0").isShared() == true + initTAddress("100.128.0.0:0").isShared() == false + + a.isGlobal() == false + initTAddress("0.0.0.0:0").isGlobal() == false + initTAddress("127.0.0.0:0").isGlobal() == false + initTAddress("10.0.0.0:0").isGlobal() == false + initTAddress("10.255.255.255:0").isGlobal() == false + initTAddress("172.16.0.0:0").isGlobal() == false + initTAddress("172.31.255.255:0").isGlobal() == false + initTAddress("192.168.0.0:0").isGlobal() == false + initTAddress("192.168.255.255:0").isGlobal() == false + initTAddress("100.64.0.0:0").isGlobal() == false + initTAddress("100.64.0.1:0").isGlobal() == false + initTAddress("100.127.255.255:0").isGlobal() == false + initTAddress("169.254.0.0:0").isGlobal() == false + initTAddress("169.254.255.255:0").isGlobal() == false + initTAddress("192.0.0.0:0").isGlobal() == false + initTAddress("192.0.0.255:0").isGlobal() == false + initTAddress("192.0.2.0:0").isGlobal() == false + initTAddress("192.0.2.255:0").isGlobal() == false + initTAddress("198.51.100.0:0").isGlobal() == false + initTAddress("198.51.100.255:0").isGlobal() == false + initTAddress("203.0.113.0:0").isGlobal() == false + initTAddress("203.0.113.255:0").isGlobal() == false + initTAddress("198.18.0.0:0").isGlobal() == false + initTAddress("198.18.0.1:0").isGlobal() == false + initTAddress("198.19.0.1:0").isGlobal() == false + initTAddress("198.19.255.255:0").isGlobal() == false + initTAddress("240.0.0.0:0").isGlobal() == false + initTAddress("250.0.0.0:0").isGlobal() == false + initTAddress("255.254.254.254:0").isGlobal() == false + initTAddress("255.255.255.255:0").isGlobal() == false + + initTAddress("1.1.1.1:0").isGlobal() == true + initTAddress("8.8.8.8:0").isGlobal() == true + test "IPv6 networks test": + var a: TransportAddress check: - initTAddress("[::]:0").isUnspecified() == false + initTAddress("[::]:0").isNone() == false + initTAddress("[::]:0").isUnspecified() == true initTAddress("[::]:0").isZero() == true initTAddress("[::1]:0").isZero() == false + initTAddress("[::1]:0").isUnspecified() == 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( + "[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( + "[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( + "[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( + "[FFFE:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isGlobalMulticast() == true initTAddress("[FF0D::]:0").isGlobalMulticast() == false initTAddress("[FFFF::]:0").isGlobalMulticast() == false + initTAddress( + "[FBFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isUniqueLocal() == false + initTAddress("[FC00::]:0").isUniqueLocal() == true + initTAddress( + "[FDFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isUniqueLocal() == true + initTAddress("[FE00::]:0").isUniqueLocal() == false + + initTAddress( + "[FE7F:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isUnicastLinkLocal() == false + initTAddress("[FE80::]:0").isUnicastLinkLocal() == true + initTAddress( + "[FEBF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isUnicastLinkLocal() == true + initTAddress("[FEC0::]:0").isUnicastLinkLocal() == false + + initTAddress( + "[2001:0001:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isBenchmarking() == false + initTAddress("[2001:0002::]:0").isBenchmarking() == true + initTAddress( + "[2001:0002:0000:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isBenchmarking() == true + initTAddress("[2001:0002:0001::]:0").isBenchmarking() == false + + initTAddress( + "[2001:0DB7:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isDocumentation() == false + initTAddress("[2001:0DB8::]:0").isDocumentation() == true + initTAddress( + "[2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isDocumentation() == true + initTAddress("[2001:0DB9::]:0").isDocumentation() == false + + a.isGlobal() == false + initTAddress("[::]:0").isGlobal() == false + initTAddress("[::1]:0").isGlobal() == false + initTAddress("[::FFFF:0000:0000]:0").isGlobal() == false + initTAddress("[::FFFF:FFFF:FFFF]:0").isGlobal() == false + initTAddress("[0064:FF9B:0001::]:0").isGlobal() == false + initTAddress( + "[0064:FF9B:0001:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isGlobal() == false + initTAddress("[0100::]:0").isGlobal() == false + initTAddress( + "[0100:0000:0000:0000:FFFF:FFFF:FFFF:FFFF]:0" + ).isGlobal() == false + initTAddress("[2001::]:0").isGlobal() == false + initTAddress( + "[2001:01FF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isGlobal() == false + initTAddress("[2001:1::1]:0").isGlobal() == true + initTAddress("[2001:1::2]:0").isGlobal() == true + initTAddress("[2001:3::]:0").isGlobal() == true + initTAddress("[2001:4:112::]:0").isGlobal() == true + initTAddress("[2001:20::]:0").isGlobal() == true + initTAddress("[2001:0db8::]:0").isGlobal() == false + initTAddress( + "[2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isGlobal() == false + initTAddress("[FC00::]:0").isGlobal() == false + initTAddress( + "[FDFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isGlobal() == false + initTAddress("[FE80::]:0").isGlobal() == false + initTAddress( + "[FEBF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0" + ).isGlobal() == false + + initTAddress("[2606:4700:4700::1111]:0").isGlobal() == true + initTAddress("[2606:4700:4700::1001]:0").isGlobal() == true + test "IP masks test": check: $IpMask.init(AddressFamily.IPv4, -1) == "00000000" @@ -481,19 +641,50 @@ suite "Network utilities test suite": route.ifIndex != 0 echo route - test "TransportAddress arithmetic operations test": + test "TransportAddress arithmetic operations test (add)": + block: + var ip4 = initTAddress("192.168.1.0:1024") + var ip6 = initTAddress("[::1]:1024") + when sizeof(int) == 8: + ip4 = ip4 + uint(0xFFFF_FFFF_FFFF_FFFF'u64) + ip6 = ip6 + uint(0xFFFF_FFFF_FFFF_FFFF'u64) + var ip6e = initTAddress("[::1:0000:0000:0000:1]:1024") + else: + ip4 = ip4 + uint(0xFFFF_FFFF'u32) + ip6 = ip6 + uint(0xFFFF_FFFF'u32) + var ip6e = initTAddress("[::1:0000:1]:1024") + inc(ip4) + inc(ip6) + check: + ip4 == initTAddress("192.168.1.0:1024") + ip6 == ip6e + ip4 = initTAddress("255.255.255.255:0") + ip6 = initTAddress("[FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0") + inc(ip4) + inc(ip6) + check: + ip4 == initTAddress("0.0.0.0:0") + ip6 == initTAddress("[::]:0") + + test "TransportAddress arithmetic operations test (sub)": var ip4 = initTAddress("192.168.1.0:1024") - var ip6 = initTAddress("[::1]:1024") when sizeof(int) == 8: - ip4 = ip4 + uint(0xFFFF_FFFF_FFFF_FFFF'u64) - ip6 = ip6 + uint(0xFFFF_FFFF_FFFF_FFFF'u64) - var ip6e = initTAddress("[::1:0000:0000:0000:1]:1024") + var ip6 = initTAddress("[::1:0000:0000:0000:0000]:1024") + ip4 = ip4 - uint(0xFFFF_FFFF_FFFF_FFFF'u64) + ip6 = ip6 - uint(0xFFFF_FFFF_FFFF_FFFF'u64) else: - ip4 = ip4 + uint(0xFFFF_FFFF'u32) - ip6 = ip6 + uint(0xFFFF_FFFF'u32) - var ip6e = initTAddress("[::1:0000:1]:1024") - inc(ip4) - inc(ip6) + var ip6 = initTAddress("[::1:0000:0000]:1024") + ip4 = ip4 - uint(0xFFFF_FFFF'u32) + ip6 = ip6 - uint(0xFFFF_FFFF'u32) + dec(ip4) + dec(ip6) check: ip4 == initTAddress("192.168.1.0:1024") - ip6 == ip6e + ip6 == initTAddress("[::]:1024") + ip4 = initTAddress("0.0.0.0:0") + ip6 = initTAddress("[::]:0") + dec(ip4) + dec(ip6) + check: + ip4 == initTAddress("255.255.255.255:0") + ip6 == initTAddress("[FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:0")