Working with IP addresses

The IP module has a lot of surface area, so this guide focuses on what to use in day-to-day code.

Use this when you want to:

For the full method-by-method reference, see the API docs.

Quick start

local ip = require("@eryx/ip")

local addr = ip.address("192.168.1.10")
local net = ip.network("192.168.1.0/24")

if addr and net and net:contains(addr) then
    print("address is inside network")
end

Which entry point should I use?

Parsing policy (strict, allowZoneId)

Parsing accepts optional options:

local netLoose = ip.network("192.168.1.42/24", { strict = false })
local netStrict = ip.network("192.168.1.42/24", { strict = true })
-- netLoose -> 192.168.1.0/24
-- netStrict -> nil (host bits were set)

local linkLocal = ip.address("fe80::1%eth0", { allowZoneId = true })

Rule of thumb:

Address objects: when to use them

Address objects are best for:

local a = ip.address("10.0.0.1")
local b = ip.address("10.0.0.2")

if a and b and a < b then
    print("sorted")
end

local net = a and (a / 24) -- address -> network via / prefix

Network objects: when to use them

Network objects are best for:

local net = ip.network("10.0.0.0/24")
if net then
    for host in net:hosts() do
        -- lazy iterator
        print(host)
    end
end

Important: iterators are lazy, but huge ranges can still run for a very long time. Prefer bounded loops and early exits.

Interface objects: when to use them

Use interface objects when you care about both host address and attached prefix as a single value.

local iface = ip.interface("192.168.1.10/255.255.255.0")
if iface then
    print(iface:withPrefixLength()) -- 192.168.1.10/24
    print(iface:withNetmask())      -- 192.168.1.10/255.255.255.0
end

Range objects: set-style IP ranges

Ranges are useful when CIDR alone is not enough (for example exclusions or irregular spans).

Constructors:

Common operations:

local r = ip.IPv4Range.new("192.168.1.10", "192.168.1.20")
local trimmed = r:exclude("192.168.1.15")

for net in trimmed:toNetworks() do
    print(net)
end

Use toNetworks() when you need exact coverage. Use smallestContainingNetwork() when one broad CIDR is acceptable.

IPv4/IPv6 interoperability patterns

Useful conversions:

local mapped = ip.address("::ffff:192.168.1.1")
if mapped and mapped:isIPv4Mapped() then
    local v4 = mapped:toIPv4()
    print(v4)
end

Sorting and summarizing

Use built-ins instead of hand-rolling comparisons:

These keep family-specific behavior explicit and avoid accidental IPv4/IPv6 mixing.

Practical workflow

A good default flow for user input:

  1. Validate with ip.address or ip.network.
  2. Normalize using tostring(...) for storage/display.
  3. Run policy checks (isPrivate, contains, etc.).
  4. Convert only when required (toIPv6Mapped, toIPv4, toPacked, toInteger).

This keeps your code predictable, fast, and easy to reason about.