whois

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 15 Imported by: 0

README

whois-golang

English | 中文

Go Reference CI Go Report Card

A zero-dependency Go library for domain registration lookup.

RDAP first, WHOIS fallback. Queries go through the rdap.org bootstrap service (built on the IANA RDAP bootstrap registry, the same dataset behind deployment.rdap.org). If the TLD has no RDAP deployment, the library automatically falls back to the classic WHOIS protocol on TCP port 43 — with a parser that actually understands the wildly different formats registries use.

google.com   → RDAP  (rdap.verisign.com)
google.de    → WHOIS (whois.denic.de)     .de has no RDAP
google.shop  → RDAP  (rdap.gmoregistry.net) .shop retired port 43 in 2026

Features

  • RDAP-first strategy — correct distinction between "TLD has no RDAP" (fall back) and "domain not registered" (report availability).
  • Battle-tested WHOIS parser — generic key/value extraction plus per-TLD rules, validated against 28 real responses captured from the official servers: com org de jp uk ru cn fr br it eu kr hk tw tr au nl us ca xyz in io me vc (and more).
    • JPRS bracket format ([Created on], p. [Name Server], expiry from [State] Connected (…))
    • Nominet block format with next-line values and IPv6-carrying name server lines
    • EURid / nic.it colon-less sections, TRABIS ** Key decoration, KISA bilingual layout, TWNIC colon-less dates, CNNIC, TCI (.ru), registro.br compact dates …
  • Availability detection — per-registry not-found patterns (Status: free, No match, AVAILABLE, No entries found, …); throttled/empty answers are reported as errors, never as "available".
  • Embedded server map — ~580 TLD → WHOIS server entries, cross-checked against whois.iana.org; unknown TLDs are discovered at runtime through the IANA referral service and cached.
  • Thin-registry referral following (optional) — enrich Verisign-style thin answers with the registrar's own WHOIS.
  • IDN support — built-in RFC 3492 punycode encoder (münchen.dexn--mnchen-3ya.de), no external dependencies.
  • IP & ASN lookups — RDAP covers every allocated IP range and AS number via the five RIRs; the WHOIS fallback resolves the responsible RIR through the whois.iana.org referral and parses ARIN/RIPE/APNIC/LACNIC formats.

Install

go get github.com/deershark/whois-golang

Usage

package main

import (
	"fmt"

	whois "github.com/deershark/whois-golang"
)

func main() {
	c := whois.New()
	rec, err := c.Query("google.jp") // .jp has no RDAP → WHOIS fallback
	if err != nil {
		panic(err)
	}
	fmt.Println(rec.Source, rec.Server)       // whois whois.jprs.jp
	fmt.Println(rec.Registered)               // true
	fmt.Println(rec.Parsed.Statuses)          // [Active]
	fmt.Println(rec.Parsed.NameServers)       // [ns1.google.com ns2.google.com ns3.google.com ns4.google.com]
	fmt.Println(rec.Parsed.Created)           // 2005-05-30
	fmt.Println(rec.Parsed.Expiry)            // 2027-05-31
}
IP and ASN lookups
rec, err := c.QueryIP("8.8.8.8") // → RDAP via rdap.arin.net
if err != nil {
	panic(err)
}
fmt.Println(rec.Source, rec.Server)              // rdap rdap.arin.net
fmt.Println(rec.Parsed.Name)                     // GOGL
fmt.Println(rec.Parsed.StartAddress, rec.Parsed.EndAddress) // 8.8.8.0 8.8.8.255
fmt.Println(rec.Parsed.CIDR)                     // 8.8.8.0/24
fmt.Println(rec.Parsed.Entities[0].Contact.Name) // Google LLC

asn, err := c.QueryASN(15169) // RDAP only (bootstrap covers every allocated ASN)
fmt.Println(asn.Parsed.Name) // GOOGLE

With RDAP disabled (WithPreferRDAP(false)), QueryIP resolves the responsible RIR through a whois.iana.org referral and parses the classic WHOIS response (ARIN NetRange/CIDR, RIPE/APNIC inetnum/netname, LACNIC …).

Options
c := whois.New(
	whois.WithTimeout(15*time.Second),        // per-request network timeout
	whois.WithPreferRDAP(true),               // try RDAP first (default)
	whois.WithWhoisFallback(true),            // fall back to port 43 (default)
	whois.WithReferralFollowing(true),        // follow "Registrar WHOIS Server" for thick data
	whois.WithRDAPBaseURL("https://rdap.verisign.com/com/v1"), // custom RDAP endpoint
	whois.WithWhoisServer("test", "127.0.0.1:1043"),           // pin a server per TLD
	whois.WithHTTPClient(customHTTP),         // custom *http.Client
)
Result
type Record struct {
	Domain      string      // normalized A-label form
	Source      Source      // "rdap" or "whois"
	Server      string      // authoritative server that answered
	Registered  bool        // false when the registry reports the domain as available
	Raw         string      // raw response (JSON for RDAP, text for WHOIS)
	RegistryRaw string      // original thin-registry answer when a referral was followed
	Parsed      *ParsedInfo // normalized fields: dates, registrar, statuses, name servers, contacts…
}
Helpers
info, registered := whois.Parse("uk", rawResponse) // offline WHOIS parsing
ok := whois.IsAvailable("de", rawResponse)
alabel, _ := whois.ToASCII("新华网.cn")              // xn--xkrr14bows.cn

Testing

go test ./...                 # unit tests (offline, fake servers, real samples)
WHOIS_LIVE=1 go test ./...    # additionally hit real registries (rate limits apply)

The testdata/ samples are genuine responses fetched from the official servers in July 2026 — they document each registry's real format, including edge cases like DENIC's two-line answers and EURid's dateless output.

Notes on coverage

  • All ~1200 TLDs in the IANA RDAP bootstrap work through the RDAP path.
  • The embedded WHOIS map covers ~580 TLDs and is cross-verified against IANA; registries that shut down port 43 (.info, .mobi, .pro, Google TLDs, GMO TLDs like .shop/.tokyo, …) are intentionally absent — they are served by RDAP.
  • Some registries (.ch, .li, .at, …) restrict port-43 access by client IP; the library surfaces their refusal as an error rather than guessing.

License

MIT

Documentation

Overview

Package whois queries domain registration data.

Lookup strategy (per https://deployment.rdap.org/ deployment status):

  1. RDAP is attempted first via the rdap.org bootstrap service, which redirects to the authoritative RDAP server for the TLD based on the IANA bootstrap registry (https://data.iana.org/rdap/dns.json).
  2. If the TLD has no RDAP deployment (rdap.org answers 404 without a redirect), the client falls back to the classic WHOIS protocol on TCP port 43. The responsible server is resolved from an embedded table, falling back to a whois.iana.org referral lookup.

WHOIS responses differ wildly per registry, so parsing is best-effort: a generic key/value parser plus per-TLD rules (jp, uk, br, eu, it, ...) normalize the common fields (dates, registrar, name servers, status).

Beyond domains, QueryIP looks up IPv4/IPv6 registrations (RDAP via the five RIRs, with a whois.iana.org-referred WHOIS fallback) and QueryASN looks up autonomous system numbers (RDAP only).

Usage:

c := whois.New()
rec, err := c.Query("example.com")
if err != nil { ... }
fmt.Println(rec.Source, rec.Registered, rec.Parsed.Expiry)

ip, err := c.QueryIP("8.8.8.8")
asn, err := c.QueryASN(15169)

The zero-dependency package also exposes Parse and ParseIPWhois (for offline WHOIS parsing) and ToASCII (IDN conversion) for reuse.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoRDAP is returned (wrapped) when the TLD has no RDAP deployment.
	// With fallback enabled this is handled internally.
	ErrNoRDAP = errors.New("whois: TLD has no RDAP service")
	// ErrNoWhoisServer is returned when no port-43 server is known for a TLD.
	ErrNoWhoisServer = errors.New("whois: no WHOIS server known for TLD")
)

Errors returned by the client.

Functions

func IsAvailable

func IsAvailable(tld, raw string) bool

IsAvailable reports whether a raw WHOIS response indicates that the domain is not registered.

func ToASCII

func ToASCII(domain string) (string, error)

ToASCII converts a (possibly internationalized) domain name to its A-label form (punycode per non-ASCII label, lower-cased).

Types

type ASNInfo added in v0.2.0

type ASNInfo struct {
	Handle      string
	Name        string
	Type        string
	Country     string
	StartAutnum uint32
	EndAutnum   uint32
	Statuses    []string
	Created     *time.Time
	Updated     *time.Time
	Entities    []EntityContact
}

ASNInfo is a normalized view of an autnum registration.

type ASNRecord added in v0.2.0

type ASNRecord struct {
	Query  uint32
	Source Source
	Server string
	Found  bool
	Raw    string
	Parsed *ASNInfo
}

ASNRecord is the result of a QueryASN lookup.

type Client

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

Client queries domain registration data. It is safe for concurrent use.

func New

func New(opts ...Option) *Client

New builds a Client with sane defaults.

func (*Client) Query

func (c *Client) Query(domain string) (*Record, error)

Query looks up domain: RDAP first (via rdap.org bootstrap), falling back to the raw WHOIS protocol for TLDs without RDAP deployment.

func (*Client) QueryASN added in v0.2.0

func (c *Client) QueryASN(asn uint32) (*ASNRecord, error)

QueryASN looks up an autonomous system number. RDAP covers every allocated ASN via the IANA bootstrap, so no WHOIS fallback is needed.

func (*Client) QueryASNContext added in v0.2.0

func (c *Client) QueryASNContext(ctx context.Context, asn uint32) (*ASNRecord, error)

QueryASNContext is QueryASN with a context.

func (*Client) QueryContext

func (c *Client) QueryContext(ctx context.Context, domain string) (*Record, error)

QueryContext is Query with a context.

func (*Client) QueryIP added in v0.2.0

func (c *Client) QueryIP(ip string) (*IPRecord, error)

QueryIP looks up the registration of an IPv4 or IPv6 address: RDAP first (rdap.org redirects to the responsible RIR), falling back to the classic WHOIS protocol via the whois.iana.org referral.

func (*Client) QueryIPContext added in v0.2.0

func (c *Client) QueryIPContext(ctx context.Context, ip string) (*IPRecord, error)

QueryIPContext is QueryIP with a context.

type Contact

type Contact struct {
	Name         string
	Organization string
	Email        string
	Phone        string
}

Contact is a normalized role contact.

type EntityContact added in v0.2.0

type EntityContact struct {
	Handle  string
	Roles   []string
	Contact Contact
}

EntityContact associates registry roles with contact data.

type IPInfo added in v0.2.0

type IPInfo struct {
	Handle       string
	Name         string
	Type         string // e.g. "DIRECT ALLOCATION", "ASSIGNED PA"
	StartAddress string
	EndAddress   string
	CIDR         string
	Version      string // "v4" or "v6" (RDAP only)
	Country      string
	ParentHandle string
	Statuses     []string
	Created      *time.Time
	Updated      *time.Time
	Organization string
	Description  string
	Entities     []EntityContact
}

IPInfo is a normalized view of an IP network registration (RDAP "ip network" object or an RIR WHOIS response).

func ParseIPWhois added in v0.2.0

func ParseIPWhois(raw string) (*IPInfo, bool)

ParseIPWhois converts a raw RIR WHOIS response into an IPInfo and reports whether the address block is registered.

type IPRecord added in v0.2.0

type IPRecord struct {
	// Query is the queried IP address in canonical form.
	Query string
	// Source tells whether RDAP or the raw WHOIS protocol answered.
	Source Source
	// Server is the RIR server that produced the final answer.
	Server string
	// Found is false when the RIR reports no registration for the address.
	Found bool
	// Raw is the raw response body (JSON for RDAP, plain text for WHOIS).
	Raw string
	// Parsed is a best-effort structured view of Raw.
	Parsed *IPInfo
}

IPRecord is the result of a QueryIP lookup.

type Option

type Option func(*Client)

Option configures a Client.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient sets the HTTP client used for RDAP requests.

func WithIANAServer

func WithIANAServer(server string) Option

WithIANAServer overrides the bootstrap WHOIS server used to discover unknown TLD servers (default whois.iana.org).

func WithPreferRDAP

func WithPreferRDAP(v bool) Option

WithPreferRDAP toggles trying RDAP before WHOIS (default true).

func WithRDAPBaseURL

func WithRDAPBaseURL(u string) Option

WithRDAPBaseURL replaces the RDAP endpoint (default https://rdap.org). A custom URL is treated as an authoritative RDAP server: a 404 then means "domain not found" instead of "TLD without RDAP".

func WithReferralFollowing

func WithReferralFollowing(v bool) Option

WithReferralFollowing toggles following the "Registrar WHOIS Server" referral found in thin-registry (com/net) responses to obtain thick data (default false, to stay polite with registrar rate limits).

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-request network timeout (default 10s).

func WithWhoisFallback

func WithWhoisFallback(v bool) Option

WithWhoisFallback toggles falling back to the WHOIS protocol when RDAP is unavailable for the TLD or fails (default true).

func WithWhoisServer

func WithWhoisServer(tld, server string) Option

WithWhoisServer pins the WHOIS server used for a given TLD (e.g. WithWhoisServer("example", "127.0.0.1:1043") for tests).

type ParsedInfo

type ParsedInfo struct {
	DomainName  string
	Handle      string // registry domain ID / ROID
	Registrar   string
	RegistrarID string // IANA registrar ID
	WhoisServer string // port-43 server advertised by the registry
	Created     *time.Time
	Updated     *time.Time
	Expiry      *time.Time
	Statuses    []string
	NameServers []string
	DNSSec      string
	Registrant  Contact
	Admin       Contact
	Tech        Contact
	Billing     Contact
	// Extra contains every parsed key/value pair (WHOIS source only),
	// keyed by lower-cased field name.
	Extra map[string][]string
}

ParsedInfo is the normalized registration data shared by RDAP and WHOIS responses. Any field may be empty when the registry does not publish it.

func Parse

func Parse(tld, raw string) (*ParsedInfo, bool)

Parse converts a raw WHOIS response for tld into a ParsedInfo and reports whether the domain appears to be registered. Parsing is best-effort: registries use wildly different formats, so a generic key/value parser is combined with per-TLD rules.

type Record

type Record struct {
	// Domain is the queried domain, normalized to its A-label form.
	Domain string
	// Source tells whether RDAP or the raw WHOIS protocol answered.
	Source Source
	// Server is the authoritative server that produced the final answer
	// (host name of the RDAP server, or the WHOIS server).
	Server string
	// Registered is false when the registry reports the domain as available.
	Registered bool
	// Raw is the raw response body (JSON for RDAP, plain text for WHOIS).
	// When a WHOIS referral was followed, this is the referral (thick) response.
	Raw string
	// RegistryRaw holds the original thin-registry WHOIS response when a
	// referral was followed; empty otherwise.
	RegistryRaw string
	// Parsed is a best-effort structured view of Raw.
	Parsed *ParsedInfo
}

Record is the result of a successful Query.

func Query

func Query(domain string) (*Record, error)

Query looks up domain with a default client.

type Source

type Source string

Source indicates which protocol produced a Record.

const (
	// SourceRDAP means the record came from an RDAP (HTTPS JSON) server.
	SourceRDAP Source = "rdap"
	// SourceWhois means the record came from a classic port-43 WHOIS server.
	SourceWhois Source = "whois"
)

Jump to

Keyboard shortcuts

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