## Nim-LibP2P ## Copyright (c) 2018 Status Research & Development GmbH ## Licensed under either of ## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) ## * MIT license ([LICENSE-MIT](LICENSE-MIT)) ## at your option. ## This file may not be copied, modified, or distributed except according to ## those terms. ## This module implements various IP network utility procedures. import endians, strutils import chronos export chronos 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``. ## ## For IPv4 mask, if ``prefix`` is ``0`` or more then ``32`` it will be set ## to ``32``. ## ## For IPv6 mask, if ``prefix`` is ``0`` or more then ``128`` it will be set ## to ``128``. if family == AddressFamily.IPv4: result = IpMask(family: AddressFamily.IPv4) 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 >= 128 or prefix <= 0: 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 ##
/