resolv

package module
v0.0.0-...-9fce615 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-resolv/resolv

resolv — go-ruby-resolv

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the deterministic, pure-compute core of Ruby's Resolv library — the DNS wire format, domain names, resource records, address parsing, and /etc/hosts parsing of MRI 4.0.5, without any Ruby runtime.

It encodes and decodes DNS messages byte-for-byte like Resolv::DNS::Message#encode / Resolv::DNS::Message.decode, parses and renders Resolv::IPv4 / Resolv::IPv6 addresses (with MRI's exact :: compression), builds Resolv::DNS::Names with length-prefixed labels and 0xC0 compression pointers, and reproduces Resolv::Hosts's name↔address tables.

It is the DNS-primitive backend for go-embedded-ruby, but is a standalone, reusable module with no dependency on the Ruby runtime — a sibling of go-ruby-regexp (the Onigmo engine) and go-ruby-marshal.

What it is — and isn't. The message/name/record encode-decode, the address grammar, and the hosts-file parse are fully deterministic and need no interpreter, so they live here as pure Go. The actual resolution — querying a server over UDP/TCP — is the host's job (rbgo wires sockets to these primitives); this library does no networking and no file I/O. Resolv::Hosts takes the file content as a string, and Resolv.getaddress (which hits the network) is out of scope.

Features

Faithful port of Resolv's pure-compute surface, validated against the ruby binary on every supported platform:

  • Resolv::DNS::Message — full header (ID, QR/Opcode/AA/TC/RD/RA/RCODE), the question section, and the answer / authority / additional record sections. Encode and Decode round-trip byte-for-byte, including the truncation-bit short-circuit MRI applies on decode.
  • Resolv::DNS::Name — dotted-name parse/print, the absolute (trailing-dot) flag, case-insensitive Equal and SubdomainOf, and wire encoding with RFC 1035 length-prefixed labels and case-insensitive 0xC0 compression pointers (with backward-pointer and 255-octet guards on decode).
  • Resource recordsA, AAAA, CNAME, NS, PTR, MX, TXT, SOA, SRV (target encoded uncompressed, per MRI), HINFO, plus a Generic fallback that round-trips any other TYPE/CLASS opaquely.
  • Resolv::IPv4 / Resolv::IPv6Create parse with the exact MRI Regex/Regex256 acceptance set, canonical String rendering (IPv6 uses MRI's first-run :: compression), the raw Addr bytes, Equal, and the exported IPv4Regex / IPv6Regex constants.
  • Resolv::Hosts — parse /etc/hosts-format text into name↔address maps and query them with GetAddress / GetAddresses / GetName / GetNames, reproducing MRI's comment stripping, whitespace split, and reversed per-name address ordering.

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-resolv/resolv

Usage

package main

import (
	"encoding/hex"
	"fmt"

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

func main() {
	// Build and encode a DNS query (Resolv::DNS::Message#encode).
	m := resolv.NewMessage(0x1234)
	m.AddQuestion(resolv.NewName("www.example.com"), resolv.TypeA, resolv.ClassIN)
	fmt.Println(hex.EncodeToString(m.Encode()))
	// 12340000000100000000000003777777076578616d706c6503636f6d0000010001

	// Decode a response and read its records (Resolv::DNS::Message.decode).
	resp, _ := resolv.Decode(m.Encode())
	fmt.Println(resp.Question[0].Name) // www.example.com

	// Parse addresses (Resolv::IPv4 / Resolv::IPv6).
	ip, _ := resolv.CreateIPv6("2001:db8:0:0:0:0:0:1")
	fmt.Println(ip)               // 2001:db8::1

	// Parse a hosts table (Resolv::Hosts).
	h := resolv.ParseHosts("127.0.0.1 localhost\n")
	addr, _ := h.GetAddress("localhost")
	fmt.Println(addr)             // 127.0.0.1
}

API

// Messages
func NewMessage(id uint16) *Message
func (m *Message) AddQuestion(name Name, typ, class uint16)
func (m *Message) AddAnswer(name Name, ttl uint32, data Resource)
func (m *Message) AddAuthority(name Name, ttl uint32, data Resource)
func (m *Message) AddAdditional(name Name, ttl uint32, data Resource)
func (m *Message) Encode() []byte
func Decode(m []byte) (*Message, error)

// Names
func NewName(s string) Name
func (n Name) String() string
func (n Name) Equal(o Name) bool
func (n Name) SubdomainOf(other Name) bool

// Addresses
func CreateIPv4(s string) (IPv4, error)
func CreateIPv6(s string) (IPv6, error)
var IPv4Regex, IPv6Regex *regexp.Regexp

// Records: A, AAAA, CNAME, NS, PTR, MX, TXT, SOA, SRV, HINFO, Generic

// Hosts
func ParseHosts(content string) *Hosts
func (h *Hosts) GetAddress(name string) (string, error)
func (h *Hosts) GetName(address string) (string, error)

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: messages, names, addresses, and hosts tables are built here and compared byte-for-byte against the system ruby (Resolv::DNS::Message#encode, Resolv::IPv4/IPv6.create, Resolv::Hosts). The oracle $stdout.binmodes and base64-frames the binary DNS bytes so Windows text-mode never pollutes them, gates on RUBY_VERSION >= "4.0", and skips itself 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-resolv/resolv 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

Index

Constants

View Source
const (
	TypeA     = 1
	TypeNS    = 2
	TypeCNAME = 5
	TypeSOA   = 6
	TypeWKS   = 11
	TypePTR   = 12
	TypeHINFO = 13
	TypeMINFO = 14
	TypeMX    = 15
	TypeTXT   = 16
	TypeAAAA  = 28
	TypeSRV   = 33
)

DNS resource record TYPE values, matching MRI's Resolv::DNS::Resource TypeValue constants.

View Source
const ClassIN = 1

ClassIN is the DNS IN class value (Resolv::DNS::Resource::IN::ClassValue == 1).

View Source
const DefaultHostsFileName = "/etc/hosts"

DefaultHostsFileName is Resolv::Hosts::DefaultFileName on a POSIX system.

Variables

View Source
var IPv4Regex = regexp.MustCompile(
	`\A(` + regex256 + `)\.(` + regex256 + `)\.(` + regex256 + `)\.(` + regex256 + `)\z`)

IPv4Regex matches the textual forms Ruby's Resolv::IPv4::Regex accepts: a dotted quad of four octets, each 0..255 with no leading zeros (anchored). It mirrors MRI's Regex256 alternation exactly.

View Source
var (

	// IPv6Regex is the composite matcher (Resolv::IPv6::Regex): a textual IPv6
	// address in any of its accepted forms. Each alternative is fully anchored,
	// so this matches exactly the strings CreateIPv6 accepts.
	IPv6Regex = regexp.MustCompile(
		`(?:\A` + regex8Hex + `\z)` +
			`|(?:\A` + regexCompressedHex + `\z)` +
			`|(?:\A` + regex6Hex4Dec + `\z)` +
			`|(?:\A` + regexCompressedHex4Dec + `\z)` +
			`|(?:\A` + regex8HexLinkLocal + `\z)` +
			`|(?:\A` + regexCompressedHexLinkLocal + `\z)`)
)

The component IPv6 regexps, mirroring MRI's Resolv::IPv6::Regex_* constants.

Functions

This section is empty.

Types

type A

type A struct {
	Address IPv4
	TTL     uint32
}

A is Resolv::DNS::Resource::IN::A — an IPv4 address record.

func (A) ClassValue

func (A) ClassValue() uint16

func (A) EncodeRData

func (a A) EncodeRData(e *MessageEncoder)

func (A) TypeValue

func (A) TypeValue() uint16

type AAAA

type AAAA struct {
	Address IPv6
	TTL     uint32
}

AAAA is Resolv::DNS::Resource::IN::AAAA — an IPv6 address record.

func (AAAA) ClassValue

func (AAAA) ClassValue() uint16

func (AAAA) EncodeRData

func (a AAAA) EncodeRData(e *MessageEncoder)

func (AAAA) TypeValue

func (AAAA) TypeValue() uint16

type CNAME

type CNAME struct{ DomainName }

CNAME is Resolv::DNS::Resource::IN::CNAME — the canonical name for an alias.

func NewCNAME

func NewCNAME(n Name) *CNAME

type DecodeError

type DecodeError struct{ Msg string }

DecodeError reports malformed DNS wire data, matching the failures MRI's Resolv::DNS::DecodeError signals (truncated input, junk in an RDATA window, a forward/over-long name pointer).

func (*DecodeError) Error

func (e *DecodeError) Error() string

type DomainName

type DomainName struct {
	Name Name
	TTL  uint32
	// contains filtered or unexported fields
}

DomainName is the shared body of the single-Name records (NS, CNAME, PTR). Each concrete type carries its own TYPE so equality and dispatch stay precise.

func (DomainName) ClassValue

func (DomainName) ClassValue() uint16

func (DomainName) EncodeRData

func (n DomainName) EncodeRData(e *MessageEncoder)

func (DomainName) TypeValue

func (n DomainName) TypeValue() uint16

type Generic

type Generic struct {
	Type  uint16
	Class uint16
	Data  []byte
	TTL   uint32
}

Generic is Resolv::DNS::Resource::Generic — an opaque record for a TYPE/CLASS pair the library does not model specially. It round-trips the raw RDATA.

func (Generic) ClassValue

func (g Generic) ClassValue() uint16

func (Generic) EncodeRData

func (g Generic) EncodeRData(e *MessageEncoder)

func (Generic) TypeValue

func (g Generic) TypeValue() uint16

type HINFO

type HINFO struct {
	CPU string
	OS  string
	TTL uint32
}

HINFO is Resolv::DNS::Resource::IN::HINFO — host CPU/OS information.

func (HINFO) ClassValue

func (HINFO) ClassValue() uint16

func (HINFO) EncodeRData

func (h HINFO) EncodeRData(e *MessageEncoder)

func (HINFO) TypeValue

func (HINFO) TypeValue() uint16

type Hosts

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

Hosts is a pure-compute port of Ruby's Resolv::Hosts: a parsed /etc/hosts table mapping names to addresses and back. It performs no file I/O — the host supplies the file content as a string (ParseHosts) — but reproduces MRI's parsing and lookup semantics, including the reversed address order per name.

func ParseHosts

func ParseHosts(content string) *Hosts

ParseHosts parses /etc/hosts-format text into a Hosts table, replicating MRI's Resolv::Hosts#lazy_initialize: each line has its comment stripped and is split on whitespace into an address and its hostnames; the per-name address lists are reversed (so the last-seen address for a name is returned first).

func (*Hosts) GetAddress

func (h *Hosts) GetAddress(name string) (string, error)

GetAddress returns the first address for name (Resolv::Hosts#getaddress), reporting an error when the name is absent.

func (*Hosts) GetAddresses

func (h *Hosts) GetAddresses(name string) []string

GetAddresses returns all addresses for name (Resolv::Hosts#getaddresses).

func (*Hosts) GetName

func (h *Hosts) GetName(address string) (string, error)

GetName returns the first hostname for address (Resolv::Hosts#getname), reporting an error when the address is absent.

func (*Hosts) GetNames

func (h *Hosts) GetNames(address string) []string

GetNames returns all hostnames for address (Resolv::Hosts#getnames).

type IPv4

type IPv4 struct {
	// Addr is the raw 4-byte address (Ruby's @address String).
	Addr [4]byte
}

IPv4 is a pure-compute port of Ruby's Resolv::IPv4: it parses and renders a dotted-quad address and holds the canonical 4-byte form (Resolv::IPv4#address).

func CreateIPv4

func CreateIPv4(s string) (IPv4, error)

CreateIPv4 builds an IPv4 from a dotted-quad string, matching Resolv::IPv4.create. It returns an error (Ruby raises ArgumentError) when the text does not match IPv4Regex or an octet is out of range.

func NewIPv4

func NewIPv4(b []byte) (IPv4, error)

NewIPv4 wraps a raw 4-byte address (Resolv::IPv4.new from wire bytes).

func (IPv4) Equal

func (ip IPv4) Equal(o IPv4) bool

Equal reports address equality (Resolv::IPv4#==).

func (IPv4) Inspect

func (ip IPv4) Inspect() string

Inspect renders Resolv::IPv4#inspect.

func (IPv4) String

func (ip IPv4) String() string

String renders the dotted-quad form (Resolv::IPv4#to_s).

func (IPv4) ToName

func (ip IPv4) ToName() Name

ToName turns the address into its in-addr.arpa reverse Name (Resolv::IPv4#to_name).

type IPv6

type IPv6 struct {
	// Addr is the raw 16-byte address (Ruby's @address String).
	Addr [16]byte
}

IPv6 is a pure-compute port of Ruby's Resolv::IPv6: it parses the textual IPv6 forms and holds the canonical 16-byte form (Resolv::IPv6#address).

MRI's Resolv::IPv6.create only consults the 8Hex, CompressedHex, 6Hex4Dec and CompressedHex4Dec forms — never the link-local %zone forms — so CreateIPv6 rejects a %zone string exactly as MRI does, even though IPv6Regex (the Resolv::IPv6::Regex constant, used by match?) accepts it.

func CreateIPv6

func CreateIPv6(s string) (IPv6, error)

CreateIPv6 builds an IPv6 from a textual address, matching Resolv::IPv6.create. It returns an error (Ruby raises ArgumentError) for input matching none of the accepted forms or with an out-of-range embedded IPv4 octet.

func NewIPv6

func NewIPv6(b []byte) (IPv6, error)

NewIPv6 wraps a raw 16-byte address (Resolv::IPv6.new from wire bytes).

func (IPv6) Equal

func (ip IPv6) Equal(o IPv6) bool

Equal reports address equality (Resolv::IPv6#==).

func (IPv6) Inspect

func (ip IPv6) Inspect() string

Inspect renders Resolv::IPv6#inspect.

func (IPv6) String

func (ip IPv6) String() string

String renders the canonical textual form (Resolv::IPv6#to_s): the eight groups in lowercase hex with no leading zeros, then MRI's first-run zero compression. A zone suffix is appended verbatim.

type Label

type Label struct {
	// Str is the label's bytes as supplied (Resolv::DNS::Label::Str#string).
	Str string
}

Label is a single DNS label (Ruby's Resolv::DNS::Label::Str). It preserves the original byte string while comparing case-insensitively over ASCII, per RFC 4343, exactly as MRI does (@downcase = string.b.downcase).

func (Label) Equal

func (l Label) Equal(o Label) bool

Equal reports case-insensitive (ASCII) label equality (Label::Str#==).

func (Label) String

func (l Label) String() string

String renders the label verbatim (Resolv::DNS::Label::Str#to_s).

type MX

type MX struct {
	Preference uint16
	Exchange   Name
	TTL        uint32
}

MX is Resolv::DNS::Resource::IN::MX — a mail exchanger.

func (MX) ClassValue

func (MX) ClassValue() uint16

func (MX) EncodeRData

func (m MX) EncodeRData(e *MessageEncoder)

func (MX) TypeValue

func (MX) TypeValue() uint16

type Message

type Message struct {
	ID         uint16
	QR         uint16
	Opcode     uint16
	AA         uint16
	TC         uint16
	RD         uint16
	RA         uint16
	RCode      uint16
	Question   []Question
	Answer     []RR
	Authority  []RR
	Additional []RR
}

Message is a pure-compute port of Ruby's Resolv::DNS::Message: a DNS header, the question section, and the three resource-record sections. It encodes to and decodes from the RFC 1035 wire format with 0xC0 name compression.

func Decode

func Decode(m []byte) (*Message, error)

Decode parses DNS wire bytes into a Message (Resolv::DNS::Message.decode). When the truncation (TC) bit is set, MRI returns after the header, so Decode does the same and leaves the sections empty.

func NewMessage

func NewMessage(id uint16) *Message

NewMessage builds an empty Message with the given ID (Resolv::DNS::Message.new).

func (*Message) AddAdditional

func (m *Message) AddAdditional(name Name, ttl uint32, data Resource)

AddAdditional appends an additional record (Resolv::DNS::Message#add_additional).

func (*Message) AddAnswer

func (m *Message) AddAnswer(name Name, ttl uint32, data Resource)

AddAnswer appends an answer record (Resolv::DNS::Message#add_answer).

func (*Message) AddAuthority

func (m *Message) AddAuthority(name Name, ttl uint32, data Resource)

AddAuthority appends an authority record (Resolv::DNS::Message#add_authority).

func (*Message) AddQuestion

func (m *Message) AddQuestion(name Name, typ, class uint16)

AddQuestion appends a question (Resolv::DNS::Message#add_question).

func (*Message) Encode

func (m *Message) Encode() []byte

Encode serialises the Message to DNS wire bytes (Resolv::DNS::Message#encode).

type MessageDecoder

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

MessageDecoder reads wire bytes with a movable limit for RDATA windows, mirroring Ruby's Resolv::DNS::Message::MessageDecoder.

type MessageEncoder

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

MessageEncoder accumulates wire bytes and a name-compression table, mirroring Ruby's Resolv::DNS::Message::MessageEncoder.

type NS

type NS struct{ DomainName }

NS is Resolv::DNS::Resource::IN::NS — an authoritative name server.

func NewNS

func NewNS(n Name) *NS

NewNS, NewCNAME and NewPTR build the single-Name records.

type Name

type Name struct {
	// Labels are the dot-separated components, most-significant first.
	Labels []Label
	// Absolute reports whether the source had a trailing dot.
	Absolute bool
}

Name is a pure-compute port of Ruby's Resolv::DNS::Name: an ordered list of labels plus an absolute flag (trailing dot). It carries no wire bytes; wire encoding/decoding (with 0xC0 compression) lives on the Message coder.

func NewName

func NewName(s string) Name

NewName builds a Name from a dotted string (Resolv::DNS::Name.create). The labels are the maximal non-dot runs (MRI's Label.split, /[^\.]+/), and the name is absolute iff the string ends in a dot.

func NewNameLabels

func NewNameLabels(labels []Label, absolute bool) Name

NewNameLabels builds a Name directly from labels and an absolute flag, as the wire decoder does (Resolv::DNS::Name.new defaults absolute=true).

func (Name) Equal

func (n Name) Equal(o Name) bool

Equal reports name equality (Resolv::DNS::Name#==): same absolute flag and case-insensitively equal labels.

func (Name) Inspect

func (n Name) Inspect() string

Inspect renders Resolv::DNS::Name#inspect, appending a dot when absolute.

func (Name) Length

func (n Name) Length() int

Length returns the label count (Resolv::DNS::Name#length).

func (Name) String

func (n Name) String() string

String renders the dotted name without a trailing dot, even when absolute (Resolv::DNS::Name#to_s).

func (Name) SubdomainOf

func (n Name) SubdomainOf(other Name) bool

SubdomainOf reports whether n is a strict subdomain of other (Resolv::DNS::Name#subdomain_of?): same absolute flag, strictly more labels, and a matching suffix.

type PTR

type PTR struct{ DomainName }

PTR is Resolv::DNS::Resource::IN::PTR — a pointer to another name.

func NewPTR

func NewPTR(n Name) *PTR

type Question

type Question struct {
	Name  Name
	Type  uint16
	Class uint16
}

Question is one entry of a Message's question section: a name plus the queried TYPE/CLASS (Resolv::DNS::Message#question yields [name, typeclass]).

type RR

type RR struct {
	Name Name
	TTL  uint32
	Data Resource
}

RR is one resource record in an answer/authority/additional section: an owner name, a TTL, and the typed RDATA (Resolv::DNS::Message#answer yields [name, ttl, data]).

type Resource

type Resource interface {
	// TypeValue is the record's DNS TYPE (Resolv::DNS::Resource::TypeValue).
	TypeValue() uint16
	// ClassValue is the record's DNS CLASS (Resolv::DNS::Resource::ClassValue).
	ClassValue() uint16
	// EncodeRData writes the record's RDATA to the encoder.
	EncodeRData(e *MessageEncoder)
}

Resource is the wire-level interface every record implements: it knows its TYPE/CLASS and how to encode/decode its RDATA. The encoder writes the owner name, type, class, TTL and a 16-bit length around EncodeRData; the decoder reads them and calls DecodeRData within the length window.

type SOA

type SOA struct {
	MName   Name
	RName   Name
	Serial  uint32
	Refresh uint32
	Retry   uint32
	Expire  uint32
	Minimum uint32
	TTL     uint32
}

SOA is Resolv::DNS::Resource::IN::SOA — a start-of-authority record.

func (SOA) ClassValue

func (SOA) ClassValue() uint16

func (SOA) EncodeRData

func (s SOA) EncodeRData(e *MessageEncoder)

func (SOA) TypeValue

func (SOA) TypeValue() uint16

type SRV

type SRV struct {
	Priority uint16
	Weight   uint16
	Port     uint16
	Target   Name
	TTL      uint32
}

SRV is Resolv::DNS::Resource::IN::SRV — a service location record. Its target name is encoded without compression, matching MRI (put_name compress: false).

func (SRV) ClassValue

func (SRV) ClassValue() uint16

func (SRV) EncodeRData

func (s SRV) EncodeRData(e *MessageEncoder)

func (SRV) TypeValue

func (SRV) TypeValue() uint16

type TXT

type TXT struct {
	Strings []string
	TTL     uint32
}

TXT is Resolv::DNS::Resource::IN::TXT — one or more character-strings.

func (TXT) ClassValue

func (TXT) ClassValue() uint16

func (TXT) EncodeRData

func (t TXT) EncodeRData(e *MessageEncoder)

func (TXT) TypeValue

func (TXT) TypeValue() uint16

Jump to

Keyboard shortcuts

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