// This file is part of netaddr. // // netaddr is free software: you can redistribute it and/or modify it under // the terms of the GNU Lesser General Public License as published by the // Free Software Foundation, either version 3 of the License, or (at your // option) any later version. // // netaddr is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public // License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with netaddr. If not, see . module netaddr import encoding.binary import math.bits import net pub struct Ipv4Addr { addr [4]u8 } // Ipv4Addr.new creates new Ipv4Addr instance from four octets. pub fn Ipv4Addr.new(a u8, b u8, c u8, d u8) Ipv4Addr { return Ipv4Addr{ addr: [a, b, c, d]! } } // Ipv4Addr.from_octets creates new Ipv4Addr instance from four-element byte array. pub fn Ipv4Addr.from_octets(addr [4]u8) Ipv4Addr { return Ipv4Addr{addr} } // Ipv4Addr.from_string parses addr string and creates new Ipv4Addr instance. // Only dotted-decimal form is allowed e.g. 203.0.113.5. pub fn Ipv4Addr.from_string(addr string) !Ipv4Addr { octets := addr.split('.') if octets.len != 4 { return error('expected 4 octets in ${addr}') } mut bytes := [4]u8{} for i := 0; i < 4; i++ { bytes[i] = u8(octets[i].parse_uint(10, 8) or { return error('${octets[i]} is not valid unsigned 8 bit integer in ${addr}') }) } return Ipv4Addr{bytes} } // Ipv4Addr.from_u32 creates new Ipv4Addr instance from unsigned 32 bit integer. pub fn Ipv4Addr.from_u32(addr u32) Ipv4Addr { mut bytes := [4]u8{} binary.big_endian_put_u32_fixed(mut bytes, addr) return Ipv4Addr{bytes} } // str returns string representation of IP address. pub fn (a Ipv4Addr) str() string { // string concatenation is much faster than interpolation return a.addr[0].str() + '.' + a.addr[1].str() + '.' + a.addr[2].str() + '.' + a.addr[3].str() } // u32 returns IP address represented as unsigned 32 bit integer. pub fn (a Ipv4Addr) u32() u32 { return binary.big_endian_u32_fixed(a.addr) } // u8_array returns IP address represented as byte array. pub fn (a Ipv4Addr) u8_array() []u8 { return a.addr[..] } // u8_array_fixed returns IP address represented as fixed size byte array. pub fn (a Ipv4Addr) u8_array_fixed() [4]u8 { return a.addr } // ipv6 returns IPv4-mapped or IPv4-compatible IPv6 address per RFC 4291. // By default returns the IPv4-mapped IPv6 address e.g. ::ffff:203.0.113.90. pub fn (a Ipv4Addr) ipv6(params Ipv4ToIpv6Params) Ipv6Addr { mut bytes := [16]u8{} if params.kind == .mapped { bytes[10] = u8(255) bytes[11] = u8(255) } bytes[12] = a.addr[0] bytes[13] = a.addr[1] bytes[14] = a.addr[2] bytes[15] = a.addr[3] return Ipv6Addr{ addr: bytes } } // bit_len returns number of bits required to represent IP address. // Example: // ``` // assert netaddr.Ipv4Addr.new(0, 0, 255, 255).bit_len() == 16 // ``` pub fn (a Ipv4Addr) bit_len() int { return bits.len_32(a.u32()) } // family returns the `net.AddrFamily` member corresponding to IP version. pub fn (a Ipv4Addr) family() net.AddrFamily { return .ip // net.AddrFamily.ip means IP version 4 } // reverse_pointer returns reverse DNS record for the IP address in .in-addr.arpa zone. pub fn (a Ipv4Addr) reverse_pointer() string { return a.str().split('.').reverse().join('.') + '.in-addr.arpa' } // is_link_local returns true if the address is reserved for link-local usage. pub fn (a Ipv4Addr) is_link_local() bool { return ipv4_link_local_network.contains(a) } // is_loopback returns true if this is a loopback address. pub fn (a Ipv4Addr) is_loopback() bool { return ipv4_loopback_network.contains(a) } // is_multicast returns true if the address is reserved for multicast use. pub fn (a Ipv4Addr) is_multicast() bool { return ipv4_multicast_network.contains(a) } // is_unicast returns true if the address is unicast. pub fn (a Ipv4Addr) is_unicast() bool { return !a.is_multicast() } // is_shared returns true if the address is allocated in shared address space. // See RFC 6598. Addresses from network 100.64.0.0/10 is both not "private" and // not "global", so is_private and is_global methods returns false for it. pub fn (a Ipv4Addr) is_shared() bool { return ipv4_public_network.contains(a) } // is_private returns true if the address is not globally reachable. pub fn (a Ipv4Addr) is_private() bool { return ipv4_private_networks.any(it.contains(a) == true) && ipv4_private_networks_exceptions.all(it.contains(a) == false) } // is_global return true if the address is globally reachable. pub fn (a Ipv4Addr) is_global() bool { return !a.is_private() && !ipv4_public_network.contains(a) } // is_reserved returns true if the address is IETF reserved. pub fn (a Ipv4Addr) is_reserved() bool { return ipv4_reserved_network.contains(a) } // is_unspecified returns true if the address is unspecified i.e. equals 0.0.0.0. pub fn (a Ipv4Addr) is_unspecified() bool { return a.addr == [4]u8{} } // is_netmask returns true if IP address is network mask. pub fn (a Ipv4Addr) is_netmask() bool { intval := (a.u32() ^ max_u32) + 1 return intval & (intval - 1) == 0 } // is_hostmask returns true if IP address is host mask. pub fn (a Ipv4Addr) is_hostmask() bool { return (a.u32() + 1) & a.u32() == 0 } // < returns true if a is lesser than b. pub fn (a Ipv4Addr) < (b Ipv4Addr) bool { return a.u32() < b.u32() } // == returns true if a equals b. pub fn (a Ipv4Addr) == (b Ipv4Addr) bool { return a.addr == b.addr } @[params] pub struct Ipv4ToIpv6Params { pub: kind Ipv6WithEmbeddedIpv4 = .mapped } // See RFC 4291 Section 2.5.5. pub enum Ipv6WithEmbeddedIpv4 { mapped // e.g. ::ffff:203.0.113.90 compat // e.g. ::203.0.113.90, deprecated per RFC 4291 Section 4 } pub struct Ipv4Net { pub: network_address Ipv4Addr network_mask Ipv4Addr host_mask Ipv4Addr broadcast_address Ipv4Addr host_address ?Ipv4Addr prefix_len int mut: current u32 } // Ipv4Net.new creates new Ipv4Net from network *addr* with given *prefix* length. pub fn Ipv4Net.new(addr Ipv4Addr, prefix int) !Ipv4Net { if prefix < 0 || prefix > 32 { return error('prefix length must be in range 0-32, not ${prefix}') } net_mask := max_u32 ^ (max_u32 >> prefix) mut net_addr := addr mut host_addr := ?Ipv4Addr(none) if (net_addr.u32() & net_mask) != net_addr.u32() { host_addr = Ipv4Addr{net_addr.u8_array_fixed()} net_addr = Ipv4Addr.from_u32(net_addr.u32() & net_mask) } host_mask := net_mask ^ max_u32 broadcast := net_addr.u32() | host_mask return Ipv4Net{ network_address: net_addr network_mask: Ipv4Addr.from_u32(net_mask) host_mask: Ipv4Addr.from_u32(host_mask) broadcast_address: Ipv4Addr.from_u32(broadcast) host_address: host_addr prefix_len: prefix current: net_addr.u32() } } // Ipv4Net.from_string parses cidr and creates new Ipv4Net. // Allowed formats are: // // * single IP address without prefix length, 32 is applied; // * network address with non-negative integer prefix length e.g. 172.16.16.0/24; // * network address with host mask: 172.16.16.0/0.0.0.255; // * network address with network mask: 172.16.16.0/255.255.255.0. // // If prefix length is greather than 32 and host bits is set in the network address // the optional `host_address` field will be filled with this host address. // The `network_address` field always will contain the real network address. pub fn Ipv4Net.from_string(cidr string) !Ipv4Net { if cidr.is_blank() { return error('network address cannot be blank') } mut net_addr_str, mut prefix_str := '', '' cidr_parts := cidr.split_nth('/', 2) if cidr_parts.len == 1 { net_addr_str, prefix_str = cidr_parts[0], '32' } else { net_addr_str, prefix_str = cidr_parts[0], cidr_parts[1] } mut net_addr := Ipv4Addr.from_string(net_addr_str) or { return error('invalid IPv4 address in ${cidr}') } mut prefix_len := 0 mut host_mask := Ipv4Addr{} mut net_mask := Ipv4Addr.from_u32(max_u32) mut host_addr := ?Ipv4Addr(none) if prefix_u64 := prefix_str.parse_uint(10, 32) { prefix_len = int(prefix_u64) if prefix_len < 32 { net_mask = Ipv4Addr.from_u32(max_u32 ^ (max_u32 >> u32(prefix_len))) } host_mask = Ipv4Addr.from_u32(net_mask.u32() ^ max_u32) } else { mut mask := Ipv4Addr.from_string(prefix_str) or { return error('invalid prefix length in ${cidr}') } if mask.is_netmask() || mask.addr == [4]u8{} || mask.addr == [4]u8{init: 255} { net_mask = mask host_mask = Ipv4Addr.from_u32(mask.u32() ^ max_u32) prefix_len = 32 - host_mask.bit_len() } else if mask.is_hostmask() { host_mask = mask prefix_len = 32 - mask.bit_len() if prefix_len < 32 { net_mask = Ipv4Addr.from_u32(max_u32 ^ (max_u32 >> u32(prefix_len))) } } else { return error('${mask} is not valid host or network mask in ${cidr}') } } if (net_addr.u32() & net_mask.u32()) != net_addr.u32() { host_addr = Ipv4Addr{net_addr.u8_array_fixed()} net_addr = Ipv4Addr.from_u32(net_addr.u32() & net_mask.u32()) } broadcast := Ipv4Addr.from_u32(net_addr.u32() | host_mask.u32()) return Ipv4Net{ network_address: net_addr network_mask: net_mask host_mask: host_mask broadcast_address: broadcast host_address: host_addr prefix_len: prefix_len current: net_addr.u32() } } // Ipv4Net.from_u32 creates new Ipv4Net from network *addr* with given *prefix* length. pub fn Ipv4Net.from_u32(addr u32, prefix int) !Ipv4Net { if prefix < 0 || prefix > 32 { return error('prefix length must be in range 0-32, not ${prefix}') } mut host_addr := ?Ipv4Addr(none) mut net_addr := addr net_mask := max_u32 ^ (max_u32 >> prefix) if (net_addr & net_mask) != net_addr { mut net_addr_bytes := [4]u8{} binary.big_endian_put_u32_fixed(mut net_addr_bytes, net_addr) host_addr = Ipv4Addr{net_addr_bytes} net_addr &= net_mask } host_mask := net_mask ^ max_u32 broadcast := net_addr | host_mask return Ipv4Net{ network_address: Ipv4Addr.from_u32(net_addr) network_mask: Ipv4Addr.from_u32(net_mask) host_mask: Ipv4Addr.from_u32(host_mask) broadcast_address: Ipv4Addr.from_u32(broadcast) host_address: host_addr prefix_len: prefix current: net_addr } } // str returns string representation of IPv4 network in CIDR format. pub fn (n Ipv4Net) str() string { return n.format(.with_prefix_len) } // format returns the IPv4 network as a string formatted according to the fmt rule. pub fn (n Ipv4Net) format(fmt Ipv4NetFormat) string { match fmt { .with_prefix_len { return n.network_address.str() + '/' + n.prefix_len.str() } .with_host_mask { return n.network_address.str() + '/' + n.host_mask.str() } .with_network_mask { return n.network_address.str() + '/' + n.network_mask.str() } } } // capacity returns a total number of addresses in the network. pub fn (n Ipv4Net) capacity() u64 { return u64(n.broadcast_address.u32() - n.network_address.u32()) + 1 } // next implements an iterator that iterates over all addresses in network // including network and broadcast addresses. // Example: // ``` // network := netaddr.Ipv4Net.from_string('10.0.10.2/29')! // for addr in network { // println(addr) // } // ``` pub fn (mut n Ipv4Net) next() ?Ipv4Addr { if n.current >= n.broadcast_address.u32() + 1 { return none } defer { n.current++ } return Ipv4Addr.from_u32(n.current) } // first returns the first usable host address in network. pub fn (n Ipv4Net) first() Ipv4Addr { if n.prefix_len in [31, 32] { return n.network_address } return Ipv4Addr.from_u32(n.network_address.u32() + 1) } // last returns the last usable host address in network. pub fn (n Ipv4Net) last() Ipv4Addr { if n.prefix_len in [31, 32] { return n.broadcast_address } return Ipv4Addr.from_u32(n.broadcast_address.u32() - 1) } // nth returns the Nth address in network. Supports negative indexes. pub fn (n Ipv4Net) nth(num i64) !Ipv4Addr { mut addr := Ipv4Addr{} if num >= 0 { addr = Ipv4Addr.from_u32(n.network_address.u32() + u32(num)) } else { addr = Ipv4Addr.from_u32(n.broadcast_address.u32() + u32(num + 1)) } if n.contains(addr) { return addr } return error('unable to get ${num}th address') } // contains returns true if IP address is in the network. pub fn (n Ipv4Net) contains(addr Ipv4Addr) bool { return n.network_address.u32() <= addr.u32() && addr.u32() <= n.broadcast_address.u32() } // overlaps returns true if network partly contains in *other*, // in other words if the networks addresses sets intersect. pub fn (n Ipv4Net) overlaps(other Ipv4Net) bool { return other.contains(n.network_address) || (other.contains(n.broadcast_address) || (n.contains(other.network_address) || (n.contains(other.broadcast_address)))) } // subnets returns iterator that iterates over the network subnets partitioned by given *prefix* length. // Example: // ``` // network := netaddr.Ipv4Net.from_string('198.51.100.0/24')! // subnets := network.subnets(26)! // for subnet in subnets { // println(subnet) // } // ``` pub fn (n Ipv4Net) subnets(prefix int) !Ipv4NetsIterator { if prefix > 32 || prefix < n.prefix_len { return error('prefix length must be in range ${n.prefix_len}-32, not ${prefix}') } return Ipv4NetsIterator{ prefix_len: prefix step: (n.host_mask.u32() + 1) >> (prefix - n.prefix_len) end: n.broadcast_address.u32() current: n.network_address.u32() } } // supernet returns IPv4 network containing the current network. pub fn (n Ipv4Net) supernet(prefix int) !Ipv4Net { if prefix < 0 || prefix > n.prefix_len { return error('prefix length must be in range 0-${n.prefix_len}, not ${prefix}') } if prefix == 0 { return n } net_addr := n.network_address.u32() & (n.network_mask.u32() << (n.prefix_len - prefix)) return Ipv4Net.from_u32(net_addr, prefix)! } // is_subnet_of returns true if *other* contains the network. pub fn (n Ipv4Net) is_subnet_of(other Ipv4Net) bool { return other.network_address.u32() <= n.network_address.u32() && other.broadcast_address.u32() >= n.broadcast_address.u32() } // is_supernet_of returns true if the network contains *other*. pub fn (n Ipv4Net) is_supernet_of(other Ipv4Net) bool { return n.network_address.u32() <= other.network_address.u32() && n.broadcast_address.u32() >= other.broadcast_address.u32() } // is_link_local returns true if the network is link-local. pub fn (n Ipv4Net) is_link_local() bool { return n.network_address.is_link_local() && n.broadcast_address.is_link_local() } // is_loopback returns true if this is a loopback network. pub fn (n Ipv4Net) is_loopback() bool { return n.network_address.is_loopback() && n.broadcast_address.is_loopback() } // is_multicast returns true if the network is reserved for multicast use. pub fn (n Ipv4Net) is_multicast() bool { return n.network_address.is_multicast() && n.broadcast_address.is_multicast() } // is_unicast returns true if the network is unicast. pub fn (n Ipv4Net) is_unicast() bool { return !n.is_multicast() } // is_shared returns true if the network is in shared address space. pub fn (n Ipv4Net) is_shared() bool { return n.network_address.is_shared() && n.broadcast_address.is_shared() } // is_private returns true if the network is not globally reachable. pub fn (n Ipv4Net) is_private() bool { return n.network_address.is_private() && n.broadcast_address.is_private() } // is_global return true if the network is globally reachable. pub fn (n Ipv4Net) is_global() bool { return !n.is_private() } // is_reserved returns true if the network is IETF reserved. pub fn (n Ipv4Net) is_reserved() bool { return n.network_address.is_reserved() && n.broadcast_address.is_reserved() } // is_unspecified returns true if the network is 0.0.0.0/32. pub fn (n Ipv4Net) is_unspecified() bool { return n.network_address.is_unspecified() && n.broadcast_address.is_unspecified() } // < returns true if the network is lesser than other network. pub fn (n Ipv4Net) < (other Ipv4Net) bool { if n.network_address != other.network_address { return n.network_address.u32() < other.network_address.u32() } if n.network_mask != other.network_mask { return n.network_mask.u32() < other.network_mask.u32() } return false } // == returns true if networks equals. pub fn (n Ipv4Net) == (other Ipv4Net) bool { return n.network_address == other.network_address && n.network_mask == n.network_mask } pub enum Ipv4NetFormat { with_prefix_len // e.g. 198.51.100.0/24 with_host_mask // e.g. 198.51.100.0/0.0.0.255 with_network_mask // e.g. 198.51.100.0/255.255.255.0 } pub struct Ipv4NetsIterator { prefix_len int step u32 end u32 mut: current u32 } // next implements the iterator interface for IP network subnets. pub fn (mut iter Ipv4NetsIterator) next() ?Ipv4Net { if iter.current >= iter.end + 1 { return none } defer { iter.current += iter.step } return Ipv4Net.from_u32(iter.current, iter.prefix_len)! }