MscmpSystNetwork (mscmp_syst_network v0.1.0)

Simple IP address handling and convenience functionality.

IP addresses in Erlang, and by extension Elixir, are expressed as tuples with an element reserved for each segment of the IP address. This works well enough, but departs from the standard CIDR notation used by most professionals. In fact, the Erlang/Elixir standard library for dealing with IP addresses only deals with addresses and sockets; excluded are representations or utilities for dealing with subnets.

This Component aims to make it simpler to work with IP addresses, allowing for CIDR notation parsing and for the ability to recognize subnets.

Naive IP Address Handling

This library exists only to provide some ease in handling IP addresses in some common manipulation scenarios. However, it is a minimal implementation of the most common kinds of operations and not a full-fledged and authoritative implementation of IP addressing or networking standards.

This library may allow the developer to present standards-breaking scenarios to which to library will happily provide answer, albeit invalid. The handling of special scenarios such as multicast, anycast, standards defined exclusions or special uses, or similar simply doesn't exist.

Therefore this library should not be relied upon as a source of IP networking knowledge.

Prior Work

The MscmpSystNetwork library was inspired by and is a re-working of Lambda, Inc.'s' and Isaac Yonemoto's net_address Elixir library. The Muse Systems Business Management System originally used the net_address library, but the library appears to no longer be maintained and so is being replaced by MscmpSystNetwork in the MuseBMS project. MscmpSystNetwork does use some code from net_address. MscmpSystNetwork offers significantly less functionality than net_address as much of that library's functionality is not needed in the MuseBMS.

This library is also influenced by Bryan Weber's inet_cidr Elixir library, though to a much lesser extent than net_address.

In the end, much of the heavy lifting is done by the Erlang :inet library.

Summary

Parsing

Parses common IP address and subnet text expressions from a string.

Parses common IP address and subnet text expressions from a string, raising an exception when there are errors.

Handles the ~i sigil for IP addresses.

Protocol Functions

Retrieves IP address from IP address structs or nil if the struct only represents a subnet/prefix.

Retrieves the network masking bits (IPv4 subnet mask or IPv6 prefix).

Retrieves the network identifying portion of an IP address.

Evaluates an IP address struct to see if it represents a specific host or not.

Tests to see if an IP host or subnet is contained by a specific subnet.

Tests if an IP address host or subnet is contained by the given range.

Evaluates an IP address struct to see if it represents an entire network or subnet rather than a host.

Converts an IP address struct implementing the MscmpSystNetwork.Protocol to its common string representation using CIDR notation.

Parsing

Link to this function

parse(addr_string)

@spec parse(String.t()) ::
  {:ok, MscmpSystNetwork.Types.addr_structs()} | {:error, MscmpSystError.t()}

Parses common IP address and subnet text expressions from a string.

For simple IP addresses, the expected format of the string parameter is the common format for such addresses. For IPv4 addresses, this would be "dotted-decimal" form. For IPv6 addresses, the expected string uses a textual representation of the address that complies with RFC 5952.

For subnet addresses the expected format in the CIDR addressing style appropriate for either IPv4 or IPv6 .

Individual host addresses may also be expressed in CIDR notation. IPv4 host addresses should use the standard "/32" designation for mask bits and IPv6 hosts in CIDR notation should use a prefix of "/128". These are the traditional or standard expressions for hosts when CIDR notation is used in this way. Note that even when the simple addressing format is used structs will have a :mask value of 32 for IPv4 or 128 for IPv6.

On a successful parse, an :ok tuple is returning including either an MscmpSystNetwork.Types.IpV4.t/0 or MscmpSystNetwork.Types.IpV6.t/0 value. If the parse fails,

Parameters

  • addr_string - Either a simple IP host address or an IP address or IP subnet in CIDR notation. Passed as a string.

Examples

IPv4 addresses

iex> MscmpSystNetwork.parse("192.168.10.10")
{:ok, %MscmpSystNetwork.Types.IpV4{address: {192, 168, 10, 10}, mask: 32}}

iex> MscmpSystNetwork.parse("10.1.1.10/32")
{:ok, %MscmpSystNetwork.Types.IpV4{address: {10, 1, 1, 10}, mask: 32}}

iex> MscmpSystNetwork.parse("10.1.1.11/8")
{:ok, %MscmpSystNetwork.Types.IpV4{address: {10, 1, 1, 11}, mask: 8}}

iex> MscmpSystNetwork.parse("172.16.0.0/16")
{:ok, %MscmpSystNetwork.Types.IpV4{address: {172, 16, 0, 0}, mask: 16}}

IPv4 Error Example

iex> MscmpSystNetwork.parse("192.618.10.14/32")
{:error,
 %MscmpSystError{
   code: :undefined_error,
   message: "Failure parsing IP address or subnet address string.",
   cause: %MatchError{term: {:error, :einval}}
 }}

IPv6 addresses

iex> MscmpSystNetwork.parse("fd9b:77f8:714d:cabb::1")
{
  :ok,
  %MscmpSystNetwork.Types.IpV6{
    address: {64923, 30712, 29005, 51899, 0, 0, 0, 1},
    mask: 128
  }
}

iex> MscmpSystNetwork.parse("fd9b:77f8:714d:cabb::20/128")
{
  :ok,
  %MscmpSystNetwork.Types.IpV6{
    address: {64923, 30712, 29005, 51899, 0, 0, 0, 32},
    mask: 128
  }
}

iex> MscmpSystNetwork.parse("fd9b:77f8:714d:cabb:0000:0000:ab67:12/64")
{
  :ok,
  %MscmpSystNetwork.Types.IpV6{
    address: {64923, 30712, 29005, 51899, 0, 0, 43879, 18},
    mask: 64
  }
}

iex> MscmpSystNetwork.parse("fd9b:77f8:714d:cabb::/64")
{
  :ok,
  %MscmpSystNetwork.Types.IpV6{
    address: {64923, 30712, 29005, 51899, 0, 0, 0, 0},
    mask: 64
  }
}

IPv6 Error Example

iex> MscmpSystNetwork.parse("fd9b:77f8:714d:qqqq::z")
{:error,
 %MscmpSystError{
   code: :undefined_error,
   message: "Failure parsing IP address or subnet address string.",
   cause: %MatchError{term: {:error, :einval}}
 }}
Link to this function

parse!(addr_string)

Parses common IP address and subnet text expressions from a string, raising an exception when there are errors.

Outside of the possibility of raising an exception, this function works the same as parse/1 in all other ways.

Parameters

  • addr_string - Either a simple IP host address or an IP address or IP subnet in CIDR notation. Passed as a string.

Examples

IPv4 addresses

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.parse!("192.168.10.10")
%MscmpSystNetwork.Types.IpV4{address: {192, 168, 10, 10}, mask: 32}
iex> MscmpSystNetwork.parse!("10.1.1.11/8")
%MscmpSystNetwork.Types.IpV4{address: {10, 1, 1, 11}, mask: 8}

IPv4 Error Example

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.parse!("192.618.10.14/32")
** (MatchError) no match of right hand side value: {:error, :einval}

IPv6 addresses

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.parse!("fd9b:77f8:714d:cabb::1")
%MscmpSystNetwork.Types.IpV6{address: {64923, 30712, 29005, 51899, 0, 0, 0, 1}, mask: 128}
iex> MscmpSystNetwork.parse!("fd9b:77f8:714d:cabb:0000:0000:ab67:12/64")
%MscmpSystNetwork.Types.IpV6{address: {64923, 30712, 29005, 51899, 0, 0, 43879, 18}, mask: 64}

IPv6 Error Example

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.parse!("fd9b:77f8:714d:qqqq::z")
** (MatchError) no match of right hand side value: {:error, :einval}
Link to this function

sigil_i(addr_string, modifiers)

Handles the ~i sigil for IP addresses.

Currently there are no modifiers accepted by this sigil. The sigil is simply a convenience which calls parse!/1.

Returns either a MscmpSystNetwork.Types.IpV4.t/0 or MscmpSystNetwork.Types.IpV6.t/0 struct.

Parameters

  • addr_string - Either a simple IP host address or an IP address or IP subnet in CIDR notation. Passed as a string.

  • modifiers - Currently there are no modifiers which are to be used with the sigil. This parameter is currently ignored.

Examples

IPv4 Addresses

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> ~i"192.168.10.10"
%MscmpSystNetwork.Types.IpV4{address: {192, 168, 10, 10}, mask: 32}
iex> ~i"10.1.1.11/8"
%MscmpSystNetwork.Types.IpV4{address: {10, 1, 1, 11}, mask: 8}

IPv4 Error Example

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> ~i"192.618.10.14/32"
** (MatchError) no match of right hand side value: {:error, :einval}

IPv6 Addresses

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> ~i"fd9b:77f8:714d:cabb::1"
%MscmpSystNetwork.Types.IpV6{address: {64923, 30712, 29005, 51899, 0, 0, 0, 1}, mask: 128}
iex> ~i"fd9b:77f8:714d:cabb:0000:0000:ab67:12/64"
%MscmpSystNetwork.Types.IpV6{address: {64923, 30712, 29005, 51899, 0, 0, 43879, 18}, mask: 64}

IPv6 Error Example

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> ~i"fd9b:77f8:714d:qqqq::z"
** (MatchError) no match of right hand side value: {:error, :einval}
Link to this function

to_struct(addr, mask \\ nil)

Turns an Erlang :inet.ip_address/0 tuple into either a MscmpSystNetwork.Types.IpV4.t/0 or MscmpSystNetwork.Types.IpV6.t/0 struct.

Raises on error.

Parameters

  • addr - a tuple representing either the IPv4 or IPv6 address to be used in constructing the new struct.

  • mask - represents either the bit length of the IPv4 subnet mask or the IPv6 prefix. This parameter is optional and if not provided or is nil will default to the single host value as appropriate for the addr type.

Examples

IPv4 Examples

iex> MscmpSystNetwork.to_struct({10, 1, 1, 15})
%MscmpSystNetwork.Types.IpV4{address: {10, 1, 1, 15}, mask: 32}
iex> MscmpSystNetwork.to_struct({10, 1, 0, 0}, 16)
%MscmpSystNetwork.Types.IpV4{address: {10, 1, 0, 0}, mask: 16}

IPv6 Examples

iex> MscmpSystNetwork.to_struct({64923, 30712, 29005, 51899, 0, 0, 0, 1})
%MscmpSystNetwork.Types.IpV6{address: {64923, 30712, 29005, 51899, 0, 0, 0, 1}, mask: 128}
iex> MscmpSystNetwork.to_struct({64923, 30712, 29005, 51899, 0, 0, 0, 0}, 64)
%MscmpSystNetwork.Types.IpV6{address: {64923, 30712, 29005, 51899, 0, 0, 0, 0}, mask: 64}

Protocol Functions

Link to this function

get_host(addr_struct)

Retrieves IP address from IP address structs or nil if the struct only represents a subnet/prefix.

Results are returned in the form of an Erlang :inet.ip_address/0 tuple or nil when the address passed to the function doesn't represent a host address.

Parameters

Examples

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.get_host(~i"192.168.20.125/24")
{192, 168, 20, 125}
iex> MscmpSystNetwork.get_host(~i"10.0.0.0/8")
nil

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.get_host(~i"fd9b:77f8:714d:cabb:0000:0000:ab67:12/64")
{64923, 30712, 29005, 51899, 0, 0, 43879, 18}
iex> MscmpSystNetwork.get_host(~i"fd9b:77f8:714d:cabb::/64")
nil
Link to this function

get_netmask(addr_struct)

Retrieves the network masking bits (IPv4 subnet mask or IPv6 prefix).

Results are returned in the form of an Erlang :inet.ip_address/0 tuple.

Parameters

Examples

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.get_netmask(~i"192.168.20.125/24")
{255, 255, 255, 0}
iex> MscmpSystNetwork.get_netmask(~i"10.1.1.12/32")
{255, 255, 255, 255}

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.get_netmask(~i"fd9b:77f8:714d:cabb:0000:0000:ab67:12/64")
{65535, 65535, 65535, 65535, 0, 0, 0, 0}
iex> MscmpSystNetwork.get_netmask(~i"fd9b:77f8:714d:cabb::20/128")
{65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}
Link to this function

get_network(addr_struct)

Retrieves the network identifying portion of an IP address.

Results are returned in the form of an Erlang :inet.ip_address/0 tuple or nil when the address passed to the function represents a single host.

Parameters

Examples

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.get_network(~i"192.168.20.125/24")
{192, 168, 20, 0}
iex> MscmpSystNetwork.get_network(~i"10.1.1.12/32")
nil

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.get_network(~i"fd9b:77f8:714d:cabb:0000:0000:ab67:12/64")
{64923, 30712, 29005, 51899, 0, 0, 0, 0}
iex> MscmpSystNetwork.get_network(~i"fd9b:77f8:714d:cabb::20/128")
nil
Link to this function

host?(addr_struct)

Evaluates an IP address struct to see if it represents a specific host or not.

If the function finds that an identifiable host is represented by the struct, the function returns true. Otherwise false.

IPv4 broadcast addresses will also return false.

Other Non-address IPs

Multicast addresses are currently not detected as will be treated as regular IP addresses. Therefore this function will return true for multicast addresses where it should return false.

Other examples of this sort of issue may exist for other, similar special cases. You should not assume that the IP address business logic in this Component is authoritative or complete. Test any special cases specifically for compliance with the area in which you are working.

Parameters

Examples

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.host?(~i"192.168.20.125/24")
true
iex> MscmpSystNetwork.host?(~i"10.0.0.0/8")
false

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.host?(~i"fd9b:77f8:714d:cabb:0000:0000:ab67:12/64")
true
iex> MscmpSystNetwork.host?(~i"fd9b:77f8:714d:cabb::/64")
false
Link to this function

in_network?(test_addr, network_addr)

Tests to see if an IP host or subnet is contained by a specific subnet.

True is returned when the test address is contained by the given network, otherwise false is returned. Any error raises an exception.

Parameters

  • test_addr - any valid IP address struct. This struct can represent an individual host or a subnet.

  • network_addr - a valid IP address struct which only represents a network. Host addresses from which a network can be extracted such as 10.1.1.113/24 will still cause an exception to be raised; only network only addresses such as 10.1.1.0/24 are accepted. See MscmpSystNetwork.get_network/1 for extracting a network address from a host address with an identifiable network.

Examples

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.in_network?(~i"10.1.1.10", ~i"10.1.0.0/16")
true
iex> MscmpSystNetwork.in_network?(~i"10.1.1.0/24", ~i"10.1.0.0/16")
true
iex> MscmpSystNetwork.in_network?(~i"10.1.1.0/24", ~i"10.1.1.0/24")
true
iex> MscmpSystNetwork.in_network?(~i"10.2.1.1/32", ~i"10.1.0.0/16")
false

IPv6 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.in_network?(
...>   ~i"fd9b:77f8:714d:cabb::20/128", ~i"fd9b:77f8:714d:cabb::/64")
true
iex> MscmpSystNetwork.in_network?(
...>   ~i"fd9b:77f8:714d:cabb:ab67::/68", ~i"fd9b:77f8:714d:cabb::/64")
true
iex> MscmpSystNetwork.in_network?(
...>   ~i"fd9b:77f8:714d:cabb::/64", ~i"fd9b:77f8:714d:cabb::/64")
true
iex> MscmpSystNetwork.in_network?(
...>   ~i"fd9b:77f8:714d:caab::20/128", ~i"fd9b:77f8:714d:cabb::/64")
false
Link to this function

in_range?(test_addr, low_addr, high_addr)

Tests if an IP address host or subnet is contained by the given range.

True is returned when the IP address is contained, otherwise false. Errors raise exceptions.

Parameters

  • test_addr - a valid IP address struct which must be fully contained by the low and high IP addresses to obtain a true result.

  • low_addr - The low address of the range. If the low_addr value is a struct identifying a host, but from which a network could be extracted, it is treated as a host only. If the struct represents only a network or subnet, the network IP address itself is treated as the lowest IP address in the range; for example 10.1.0.0/16 will treat 10.1.0.0 as the lowest IP address in the range.

  • high_addr - the high address of the range. If the high_addr value is a struct identifying a host, but from which a network could be extracted, it is treated as a host only. If the struct represents only a network or subnet, the network's largest possible IP address is considered the high address of the range; for example 10.1.0.0/16 would consider 10.1.255.255 as the highest address in the range.

The range boundaries are considered inclusive at both extremes.

Examples

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.in_range?(~i"10.1.1.10", ~i"10.1.1.1", ~i"10.1.1.15")
true
iex> MscmpSystNetwork.in_range?(~i"10.1.1.0/24", ~i"10.1.0.0/16", ~i"10.1.2.254")
true
iex> MscmpSystNetwork.in_range?(~i"10.1.1.0/24", ~i"10.1.1.0/24", ~i"10.1.1.0/24")
true
iex> MscmpSystNetwork.in_range?(~i"10.3.1.1/32", ~i"10.1.0.0/16", ~i"10.2.0.0/16")
false

IPv6 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.in_range?(
...>   ~i"fd9b:77f8:714d:cabb::20",
...>   ~i"fd9b:77f8:714d:cabb::10",
...>   ~i"fd9b:77f8:714d:cabb::30")
true
iex> MscmpSystNetwork.in_range?(
...>   ~i"fd9b:77f8:714d:cabb:ab67::/68",
...>   ~i"fd9b:77f8:714d:cabb::/64",
...>   ~i"fd9b:77f8:714d:cabd::")
true
iex> MscmpSystNetwork.in_range?(
...>   ~i"fd9b:77f8:714d:cabb::/64",
...>   ~i"fd9b:77f8:714d:cabb::/64",
...>   ~i"fd9b:77f8:714d:cabb::/64")
true
iex> MscmpSystNetwork.in_range?(
...>   ~i"fd9b:77f8:714e:caab::20/128",
...>   ~i"fd9b:77f8:714d:caba::/64",
...>   ~i"fd9b:77f8:714d:cabc::/64")
false
Link to this function

network?(addr_struct)

Evaluates an IP address struct to see if it represents an entire network or subnet rather than a host.

This function only returns true when the supplied IP address represents only a network or subnet. False is returned when the provided IP address struct also contains an identifiable host or is only a host. False is even returned in cases where the CIDR notation would allow a network to be extracted from the provided IP address struct.

Other Non-address IPs

Multicast addresses are currently not detected as will be treated as regular IP addresses. Therefore this function will return true for multicast addresses where it should return false.

You should not assume that the IP address business logic in this Component is authoritative or complete. Test any special cases specifically for compliance with the area in which you are working.

Parameters

Examples

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.network?(~i"192.168.20.125/24")
false
iex> MscmpSystNetwork.network?(~i"10.0.0.0/8")
true

IPv4 Examples

iex> import MscmpSystNetwork, only: [sigil_i: 2]
iex> MscmpSystNetwork.network?(~i"fd9b:77f8:714d:cabb:0000:0000:ab67:12/64")
false
iex> MscmpSystNetwork.network?(~i"fd9b:77f8:714d:cabb::/64")
true
Link to this function

to_string(addr_struct)

Converts an IP address struct implementing the MscmpSystNetwork.Protocol to its common string representation using CIDR notation.

Parameters

Examples

IPv4 Addresses

iex> my_host = %MscmpSystNetwork.Types.IpV4{address: {192, 168, 10, 10}, mask: 32}
iex> MscmpSystNetwork.to_string(my_host)
"192.168.10.10/32"
iex> my_subnet = %MscmpSystNetwork.Types.IpV4{address: {172, 16, 0, 0}, mask: 16}
iex> MscmpSystNetwork.to_string(my_subnet)
"172.16.0.0/16"

IPv6 Addresses

iex> my_host =
...>   %MscmpSystNetwork.Types.IpV6{
...>     address: {64923, 30712, 29005, 51899, 0, 0, 0, 1},
...>     mask: 128
...>   }
iex> MscmpSystNetwork.to_string(my_host)
"fd9b:77f8:714d:cabb::1/128"
iex> my_subnet =
...>   %MscmpSystNetwork.Types.IpV6{
...>     address: {64923, 30712, 29005, 51899, 0, 0, 43879, 18},
...>     mask: 64
...>   }
iex> MscmpSystNetwork.to_string(my_subnet)
"fd9b:77f8:714d:cabb::ab67:12/64"