ipaddr

package module
v0.0.0-...-ac3e17f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

go-ruby-ipaddr/ipaddr

ipaddr — go-ruby-ipaddr

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's ipaddr standard library — MRI 4.0.5's IPAddr class. It models an IP address together with a netmask (IPv4 or IPv6) and reproduces MRI's parsing, masking, string formatting, set predicates, bitwise operators and comparison semantics byte-for-byte, without any Ruby runtime.

It is the IPAddr backend for go-embedded-ruby, but is a standalone, reusable module — a sibling of go-ruby-regexp (the Onigmo engine), go-ruby-erb (the ERB compiler) and go-ruby-yaml (the Psych port).

Why it's deterministic. Parsing, masking and formatting IP addresses is a pure, interpreter-independent computation, so it lives here as pure Go. The arithmetic is carried in math/big.Int, so the full 128-bit IPv6 space and MRI's unbounded-integer behaviour (e.g. IPAddr.new("255.255.255.255").succ raising invalid address: 4294967296) are matched exactly. Go's net/netip could supply the address math, but MRI's to_s collapsing rules, inspect form, error classes and masking edge-cases are reproduced directly so the API is MRI-faithful rather than netip-faithful.

Features

Faithful port of IPAddr, validated against the ruby binary on every platform:

  • Parsing"a.b.c.d", "addr/prefixlen", "addr/netmask", bracketed IPv6 ("[::1]/64"), %zone identifiers, embedded IPv4 tails ("::ffff:1.2.3.4", "1:2:3:4:5:6:1.2.3.4"), with MRI's ambiguous-zero-fill and out-of-range octet rejections.
  • Formattingto_s (compact, with MRI's exact zero-run collapsing and the ::a.b.c.d / ::ffff:a.b.c.d rewrites), to_string (canonical expanded), cidr, inspect (#<IPAddr: IPv4:…/mask>), netmask.
  • Maskingmask(prefixlen|netmask), prefix / prefix=, masked construction; non-contiguous-netmask and leading-zero-prefix rejection.
  • Set membershipinclude? / ===, to_range, plus an idiomatic Each over the range.
  • Bitwise&, |, ~, +, -, succ, and Xor (the natural completion of the &/| set), all coercing strings / integers / IPAddr.
  • Comparison<=> (Comparable), ==, eql?-style Hash.
  • Predicatesipv4?, ipv6?, loopback?, private?, link_local?, multicast?, ipv4_mapped?, ipv4_compat?.
  • Conversionsnative, ipv4_mapped, ipv4_compat, hton, ntop, new_ntoh, family, to_i.
  • ErrorsInvalidAddressError, InvalidPrefixError, AddressFamilyError (under a base Error), with MRI's exact messages.

CGO-free, dependency-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) and three OSes (Linux, macOS, Windows).

Install

go get github.com/go-ruby-ipaddr/ipaddr

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-ipaddr/ipaddr"
)

func main() {
	net, _ := ipaddr.New("192.168.1.5/24")
	fmt.Println(net.ToS())   // 192.168.1.0   (masked, like MRI)
	fmt.Println(net.Cidr())  // 192.168.1.0/24

	in, _ := net.Include("192.168.1.99")
	fmt.Println(in)          // true

	v6, _ := ipaddr.New("::1")
	fmt.Println(v6.Ipv6())   // true

	merged, _ := mustOr(ipaddr.New("10.0.0.0/8")).Or(0x00010203)
	fmt.Println(merged.ToS()) // 10.1.2.3

	lo, hi, _ := net.ToRange()
	fmt.Println(lo.ToS(), hi.ToS()) // 192.168.1.0 192.168.1.255
}

func mustOr(ip *ipaddr.IPAddr, _ error) *ipaddr.IPAddr { return ip }

API

// Construction.
func New(s string) (*IPAddr, error)                       // IPAddr.new(s)
func NewFamily(s string, family Family) (*IPAddr, error)   // IPAddr.new(s, family)
func NewFromInt(addr *big.Int, family Family) (*IPAddr, error)
func NewNtoh(addr []byte) (*IPAddr, error)                 // IPAddr.new_ntoh
func Ntop(addr []byte) (string, error)                     // IPAddr.ntop

// Strings.
func (ip *IPAddr) ToS() string        // to_s   (compact)
func (ip *IPAddr) ToString() string   // to_string (canonical)
func (ip *IPAddr) Cidr() string       // cidr
func (ip *IPAddr) Inspect() string    // inspect
func (ip *IPAddr) Netmask() string    // netmask

// Masking / prefix.
func (ip *IPAddr) Mask(prefixlen string) (*IPAddr, error) // mask("24"|"255.255.255.0")
func (ip *IPAddr) MaskLen(prefixlen int) (*IPAddr, error)
func (ip *IPAddr) Prefix() int
func (ip *IPAddr) SetPrefix(prefix int) (*IPAddr, error)  // prefix=

// Membership / range.
func (ip *IPAddr) Include(other any) (bool, error)        // include? / ===
func (ip *IPAddr) ToRange() (lo, hi *IPAddr, err error)   // to_range
func (ip *IPAddr) Each(fn func(*IPAddr) error) error

// Bitwise / arithmetic (other: *IPAddr | string | int | int64 | uint64 | *big.Int).
func (ip *IPAddr) And(other any) (*IPAddr, error)         // &
func (ip *IPAddr) Or(other any) (*IPAddr, error)          // |
func (ip *IPAddr) Xor(other any) (*IPAddr, error)         // ^ (extension)
func (ip *IPAddr) Not() (*IPAddr, error)                  // ~
func (ip *IPAddr) Add(offset int64) (*IPAddr, error)      // +
func (ip *IPAddr) Sub(offset int64) (*IPAddr, error)      // -
func (ip *IPAddr) Succ() (*IPAddr, error)                 // succ

// Comparison.
func (ip *IPAddr) Cmp(other any) (int, bool)              // <=>  (ok=false ~> nil)
func (ip *IPAddr) Eql(other any) bool                     // ==
func (ip *IPAddr) Hash() uint64

// Predicates.
func (ip *IPAddr) Ipv4() bool
func (ip *IPAddr) Ipv6() bool
func (ip *IPAddr) Loopback() bool
func (ip *IPAddr) Private() bool
func (ip *IPAddr) LinkLocal() bool
func (ip *IPAddr) Multicast() bool        // extension: MRI 4.0.5 has no multicast?
func (ip *IPAddr) IsIpv4Mapped() bool     // ipv4_mapped?
func (ip *IPAddr) IsIpv4Compat() bool     // ipv4_compat?

// Conversions.
func (ip *IPAddr) Native() (*IPAddr, error)
func (ip *IPAddr) Ipv4Mapped() (*IPAddr, error)
func (ip *IPAddr) Ipv4Compat() (*IPAddr, error)
func (ip *IPAddr) HtonString() ([]byte, error)  // hton
func (ip *IPAddr) Family() Family
func (ip *IPAddr) ToI() *big.Int

// Errors (Error is the base; InvalidPrefixError is an InvalidAddressError in MRI).
type Error struct{ Msg string }
type InvalidAddressError struct{ Msg string }
type InvalidPrefixError struct{ Msg string }
type AddressFamilyError struct{ Msg string }

Notes on parity

  • multicast? and ^ do not exist on MRI 4.0.5's IPAddr; Multicast and Xor are provided here as natural extensions and are flagged as such above.
  • Each has no MRI counterpart either (MRI's IPAddr is not Enumerable); it is the idiomatic Go iteration over to_range that the host binds to a block.
  • Family() returns AFInet (2) / AFInet6 (10). MRI stores the host's Socket::AF_INET6, whose integer varies by OS (10 on Linux, 30 on the BSDs); use Ipv4() / Ipv6() for portable checks.
  • Hash() matches MRI's IPv4/IPv6 parity bit; the upper bits are a stable in-process mix (MRI's Array#hash is interpreter-specific), so it is for in-process Hash/Set keying, not cross-runtime equality.

Tests & coverage

The suite pairs deterministic, ruby-free tests (which alone hold coverage at 100%, so the qemu cross-arch and Windows lanes pass the gate) with a differential MRI oracle: a wide corpus is parsed and formatted here and the results compared to the system ruby -ripaddr (to_s / to_string / cidr / inspect, the predicates, the operators, to_range, the conversions, and the error class + message). The oracle scripts $stdout.binmode / $stdin.binmode so Windows text-mode never pollutes the bytes, gate on RUBY_VERSION >= "4.0", and skip themselves where ruby is absent.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-ipaddr/ipaddr authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package ipaddr is a pure-Go (no cgo) reimplementation of Ruby's `ipaddr` standard library — MRI 4.0.5's IPAddr class. It models an IP address together with a netmask (IPv4 or IPv6) and reproduces MRI's parsing, masking, string formatting (to_s / to_string / cidr / inspect), set predicates, bitwise operators and comparison semantics byte-for-byte.

Addresses and netmasks are carried in a fixed-width native-integer representation (see [u128]): IPv4 in 32 bits, IPv6 in 128, with masks, arithmetic and comparisons done in machine words so the common paths never allocate. MRI's unbounded-integer edges — the succ/`+` overflow that raises "invalid address: 4294967296", the packed-integer constructors and #to_i — are handled through a math/big.Int bridge used only where arbitrary precision is genuinely required. The representation is endianness-independent.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Ntop

func Ntop(addr []byte) (string, error)

Ntop converts a packed network-byte-ordered address (4 or 16 bytes) to its readable form, mirroring IPAddr.ntop. A []byte carries no Ruby encoding, so it is treated as BINARY (Encoding::ASCII_8BIT): a length other than 4 or 16 raises AddressFamilyError, exactly as MRI does for a BINARY-encoded String.

func NtopString

func NtopString(s, encoding string) (string, error)

NtopString mirrors IPAddr.ntop for a Ruby String argument, honouring MRI's encoding precedence: the encoding is checked *before* the byte length.

MRI raises InvalidAddressError "invalid encoding (given <enc>, expected BINARY)" for any String whose encoding is not Encoding::ASCII_8BIT/BINARY — and it does so even when the length would otherwise be valid (e.g. a 4-byte US-ASCII string). Only once the encoding is BINARY does it dispatch on length, raising AddressFamilyError for a length other than 4 or 16. encoding is the Ruby encoding name of s (e.g. "UTF-8", "US-ASCII", "ASCII-8BIT", "BINARY"); the canonical BINARY aliases are "ASCII-8BIT" and "BINARY".

Types

type AddressFamilyError

type AddressFamilyError struct{ Msg string }

AddressFamilyError mirrors IPAddr::AddressFamilyError.

func (*AddressFamilyError) Error

func (e *AddressFamilyError) Error() string

type Error

type Error struct{ Msg string }

Error is the base class of every error this package raises, mirroring IPAddr::Error < ArgumentError.

func (*Error) Error

func (e *Error) Error() string

type Family

type Family int

Family is an address family, mirroring the value IPAddr#family returns. MRI stores Socket::AF_INET / Socket::AF_INET6 there; this port uses the canonical constants below. Compare with IPAddr.Ipv4/IPAddr.Ipv6 for family-independent checks.

const (
	// AFInet is the IPv4 address family (Socket::AF_INET == 2 on every platform).
	AFInet Family = 2
	// AFInet6 is the IPv6 address family. MRI uses the host's Socket::AF_INET6
	// (which varies: 10 on Linux, 30 on the BSDs/macOS). The exact integer is an
	// implementation detail; use [IPAddr.Ipv6]. We expose Linux's value as the
	// canonical one.
	AFInet6 Family = 10
)

type IPAddr

type IPAddr struct {
	// contains filtered or unexported fields
}

IPAddr is a Ruby IPAddr: an address family plus the address and netmask, all carried in the fixed-width native-integer representation [u128].

func New

func New(addr string) (*IPAddr, error)

New parses a human-readable IP address, mirroring IPAddr.new(addr). It accepts "address", "address/prefixlen" and "address/netmask"; an IPv6 address may be wrapped in square brackets and may carry a %zone suffix. When a prefix or mask is given the address is masked. It is the string-argument form of MRI's initialize; for the packed-integer form use NewFromInt.

func NewFamily

func NewFamily(addr string, family Family) (*IPAddr, error)

NewFamily parses like New but forces the address family (Socket::AF_INET / AF_INET6), raising AddressFamilyError on a mismatch — the two-argument IPAddr.new(addr, family) form for string addresses.

func NewFromInt

func NewFromInt(addr *big.Int, family Family) (*IPAddr, error)

NewFromInt builds an IPAddr from a packed integer address and an explicit family, mirroring IPAddr.new(integer, family). family must be AFInet or AFInet6.

func NewNtoh

func NewNtoh(addr []byte) (*IPAddr, error)

NewNtoh builds an IPAddr from a packed network-byte-ordered address, mirroring IPAddr.new_ntoh.

func (*IPAddr) Add

func (ip *IPAddr) Add(offset int64) (*IPAddr, error)

Add returns a new IPAddr greater by offset, mirroring IPAddr#+.

func (*IPAddr) And

func (ip *IPAddr) And(other any) (*IPAddr, error)

And returns a new IPAddr built by bitwise AND, mirroring IPAddr#&. other may be an *IPAddr, a string, an int/int64/uint64 or a *big.Int.

func (*IPAddr) Cidr

func (ip *IPAddr) Cidr() string

Cidr returns "address/prefix", mirroring IPAddr#cidr.

func (*IPAddr) Cmp

func (ip *IPAddr) Cmp(other any) (int, bool)

Cmp compares two addresses, mirroring IPAddr#<=> (Comparable). It returns (result, true) where result is -1, 0 or 1; ok is false when the operands are incomparable (different family or an uncoercible operand), matching MRI's nil.

func (*IPAddr) Each

func (ip *IPAddr) Each(fn func(*IPAddr) error) error

Each iterates every address in the network range, lowest first, invoking fn with a host-masked IPAddr for each. MRI's IPAddr has no #each; this is the idiomatic Go iteration over to_range that rbgo binds to an each block. fn may return an error to stop iteration early.

func (*IPAddr) Eql

func (ip *IPAddr) Eql(other any) bool

Eql reports value equality, mirroring IPAddr#==: same family and same integer address. A coercion failure yields false (not an error), as MRI's rescue does.

func (*IPAddr) Family

func (ip *IPAddr) Family() Family

Family returns the address family integer, mirroring IPAddr#family.

func (*IPAddr) Hash

func (ip *IPAddr) Hash() uint64

Hash returns a hash value used for Hash/Set membership, mirroring IPAddr#hash: ([@addr, @mask_addr, @zone_id].hash << 1) | (ipv4? ? 0 : 1). The high bits derive from a stable FNV-style mix of the operands; only the parity bit is guaranteed to match MRI (the array hash itself is interpreter-specific), so Hash is for in-process Set/Hash keying, not cross-runtime equality.

func (*IPAddr) HtonString

func (ip *IPAddr) HtonString() ([]byte, error)

HtonString returns the network-byte-ordered packed form, mirroring IPAddr#hton (4 bytes for IPv4, 16 for IPv6). The byte order is written with explicit shifts, so it is identical on little- and big-endian hosts.

func (*IPAddr) Include

func (ip *IPAddr) Include(other any) (bool, error)

Include reports whether other is contained in this address's range, mirroring IPAddr#include? (aliased as ===). A non-IPAddr operand is coerced; a different family yields false rather than an error (a bad coercion is reported as err).

func (*IPAddr) Inspect

func (ip *IPAddr) Inspect() string

Inspect mirrors IPAddr#inspect: "#<IPAddr: family:address/mask>".

func (*IPAddr) Ipv4

func (ip *IPAddr) Ipv4() bool

Ipv4 reports whether the address is IPv4, mirroring IPAddr#ipv4?.

func (*IPAddr) Ipv4Compat

func (ip *IPAddr) Ipv4Compat() (*IPAddr, error)

Ipv4Compat converts a native IPv4 address into an IPv4-compatible IPv6 address, mirroring IPAddr#ipv4_compat (obsolete in MRI but reproduced).

func (*IPAddr) Ipv4Mapped

func (ip *IPAddr) Ipv4Mapped() (*IPAddr, error)

Ipv4Mapped converts a native IPv4 address into an IPv4-mapped IPv6 address, mirroring IPAddr#ipv4_mapped.

func (*IPAddr) Ipv6

func (ip *IPAddr) Ipv6() bool

Ipv6 reports whether the address is IPv6, mirroring IPAddr#ipv6?.

func (*IPAddr) IsIpv4Compat

func (ip *IPAddr) IsIpv4Compat() bool

IsIpv4Compat is the exported predicate for ipv4_compat?.

func (*IPAddr) IsIpv4Mapped

func (ip *IPAddr) IsIpv4Mapped() bool

IsIpv4Mapped is the exported predicate for ipv4_mapped?.

func (*IPAddr) LinkLocal

func (ip *IPAddr) LinkLocal() bool

LinkLocal mirrors IPAddr#link_local?. IPv4 169.254.0.0/16, IPv6 fe80::/10, plus the IPv4-mapped form.

func (*IPAddr) Loopback

func (ip *IPAddr) Loopback() bool

Loopback mirrors IPAddr#loopback?. IPv4 127.0.0.0/8, IPv6 ::1, and the IPv4-mapped 127.0.0.0/8 are loopback.

func (*IPAddr) Mask

func (ip *IPAddr) Mask(prefixlen string) (*IPAddr, error)

Mask returns a new IPAddr built by masking with the given prefix length or netmask string, mirroring IPAddr#mask. Accepts "8", "64", "255.255.255.0", etc.

func (*IPAddr) MaskLen

func (ip *IPAddr) MaskLen(prefixlen int) (*IPAddr, error)

MaskLen returns a new IPAddr masked to the given integer prefix length.

func (*IPAddr) Multicast

func (ip *IPAddr) Multicast() bool

Multicast reports whether the address is multicast (IPv4 224.0.0.0/4, IPv6 ff00::/8). MRI 4.0.5's IPAddr has no #multicast?; this is provided for completeness and follows the conventional definitions.

func (*IPAddr) Native

func (ip *IPAddr) Native() (*IPAddr, error)

Native converts an IPv4-mapped or IPv4-compatible IPv6 address back to native IPv4, mirroring IPAddr#native. Any other address is returned unchanged.

func (*IPAddr) Netmask

func (ip *IPAddr) Netmask() string

Netmask returns the netmask as a string, mirroring IPAddr#netmask.

func (*IPAddr) Not

func (ip *IPAddr) Not() (*IPAddr, error)

Not returns a new IPAddr built by bitwise negation (width-masked), mirroring IPAddr#~.

func (*IPAddr) Or

func (ip *IPAddr) Or(other any) (*IPAddr, error)

Or returns a new IPAddr built by bitwise OR, mirroring IPAddr#|.

func (*IPAddr) Prefix

func (ip *IPAddr) Prefix() int

Prefix returns the prefix length in bits, mirroring IPAddr#prefix.

func (*IPAddr) Private

func (ip *IPAddr) Private() bool

Private mirrors IPAddr#private?. IPv4 RFC1918 ranges and IPv6 fc00::/7, plus their IPv4-mapped forms.

func (*IPAddr) SetPrefix

func (ip *IPAddr) SetPrefix(prefix int) (*IPAddr, error)

SetPrefix sets the prefix length in bits, mirroring IPAddr#prefix=. It mutates the receiver (masking the address) and returns it for convenience.

func (*IPAddr) String

func (ip *IPAddr) String() string

String makes IPAddr satisfy fmt.Stringer, returning the to_s form.

func (*IPAddr) Sub

func (ip *IPAddr) Sub(offset int64) (*IPAddr, error)

Sub returns a new IPAddr less by offset, mirroring IPAddr#-.

func (*IPAddr) Succ

func (ip *IPAddr) Succ() (*IPAddr, error)

Succ returns the successor address, mirroring IPAddr#succ. As in MRI, the successor of the broadcast address raises InvalidAddressError because the incremented value overflows the family width.

func (*IPAddr) ToI

func (ip *IPAddr) ToI() *big.Int

ToI returns the integer representation of the address, mirroring IPAddr#to_i.

func (*IPAddr) ToRange

func (ip *IPAddr) ToRange() (*IPAddr, *IPAddr, error)

ToRange returns the [begin, end] IPAddr pair spanning the network, mirroring IPAddr#to_range (each endpoint carries a host mask). Use IPAddr.Each to iterate the addresses.

func (*IPAddr) ToS

func (ip *IPAddr) ToS() string

ToS returns the compact, human-readable string, mirroring IPAddr#to_s — IPv4 dotted-quad, IPv6 with leading zeros stripped and the longest run of zero groups collapsed to "::" (RFC 5952, byte-identical to MRI), including the ::a.b.c.d / ::ffff:a.b.c.d embedded-IPv4 forms. The zero-collapse is a direct scan over the eight hextets — no regexp.

func (*IPAddr) ToString

func (ip *IPAddr) ToString() string

ToString returns the canonical-form string, mirroring IPAddr#to_string (IPv6 is fully expanded, with the zone id appended).

func (*IPAddr) Xor

func (ip *IPAddr) Xor(other any) (*IPAddr, error)

Xor returns a new IPAddr built by bitwise XOR (width-masked). MRI's IPAddr has no ^ operator; this is provided as the natural completion of the bitwise set and mirrors the &/| coercion rules.

type InvalidAddressError

type InvalidAddressError struct{ Msg string }

InvalidAddressError mirrors IPAddr::InvalidAddressError.

func (*InvalidAddressError) Error

func (e *InvalidAddressError) Error() string

type InvalidPrefixError

type InvalidPrefixError struct{ Msg string }

InvalidPrefixError mirrors IPAddr::InvalidPrefixError, which in MRI is a subclass of InvalidAddressError.

func (*InvalidPrefixError) Error

func (e *InvalidPrefixError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL