Documentation
¶
Overview ¶
Package activedns is a client for the ActiveDNS API (https://activedns.net): DNS search by domain, address, network, and AS number.
client, err := activedns.NewClient("mytool/1.0")
if err != nil {
return err
}
page, err := client.Query(ctx, "*.example.com")
if err != nil {
return err
}
for _, record := range page.Records {
fmt.Println(record.Domain, record.IPAddress)
}
Queries ¶
A query is one of:
example.com records of a domain *.example.com records of a domain and of every name under it 192.0.2.1 names that resolve to an address 192.0.2.0/24 names that resolve into a network AS64496 names that resolve into an AS
Client.Query runs a query and Client.QueryCombined matches a domain, a network and an AS number at once.
Zones ¶
Client.Zone returns the zone of a name: its nameservers, NS sets, SOA and DNSSEC keys, each with the number of zones that share it. Client.Zones lists those zones, a page at a time like a query (Client.NextZones continues it), and Client.Nameservers returns the nameservers at an address.
info, err := client.Zone(ctx, "www.example.com")
if err == nil && info.Zone != nil {
pages, err := client.Zones(ctx, activedns.ByNameserver, info.Zone.Nameservers[0].Name)
...
}
Paging ¶
A query returns one Page: up to 100 records, or as many as WithPageSize asks for. The page says whether that was everything:
- Page.Count is how many records match in all.
- Page.HasMore is true when there are records after this page.
Client.Next fetches the page after one. The client never fetches further pages by itself: how much of a large result to retrieve is the caller's decision.
page, err := client.Query(ctx, "*.example.com")
for err == nil {
use(page.Records)
if !page.HasMore || enough() {
break
}
page, err = client.Next(ctx, page)
}
Tokens ¶
Requests are authenticated with a token. The SDK has one built in, shared by all its users, so no account is needed. It allows domain, wildcard, address and network queries, small pages and the first records of a result. Its rate limit is counted per client network address.
WithToken sets a token issued to you. Such a token adds AS-number and combined queries, wider networks, larger pages, deeper paging and a higher rate limit that is counted per token, not per address. Request one at https://activedns.net/contact/.
A query that the token does not allow fails with a 403; see IsForbidden.
Errors ¶
- *RateLimitError: a rate limit is reached and the client did not wait it out.
- *APIError: any other error status. IsUnauthorized and IsForbidden classify it.
- ErrNoMore: returned by Client.Next and Client.NextZones for the last page of a result.
Retries and rate limits ¶
A Client is safe for concurrent use and paces its own requests:
- One request is in flight at a time (WithMaxConcurrent).
- 429, 502, 503, 504 and connection failures are retried up to four times (WithMaxRetries) with exponential backoff, and no sooner than Retry-After. The pause applies to every goroutine using the client.
- A wait longer than 30 seconds (WithMaxWait) is not slept through. The call returns a *RateLimitError, and later calls fail without being sent until its RetryAt.
- When a response reports that no requests are left, the next request waits for the limit to reset.
- After a 401 no further requests are sent.
- Other errors, timed-out requests and timed-out searches are not retried.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
activedns "github.com/activedns/activedns-go"
)
func main() {
client, err := activedns.NewClient("mytool/1.0")
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
page, err := client.Query(ctx, "example.com")
if err != nil {
log.Fatal(err)
}
for _, record := range page.Records {
fmt.Println(record.Domain, record.IPAddress, record.Observed.Format(time.DateOnly))
}
}
Output:
Index ¶
- Constants
- Variables
- func IsForbidden(err error) bool
- func IsUnauthorized(err error) bool
- type APIError
- type Address
- type AddressNameserver
- type Alias
- type Client
- func (c *Client) Nameservers(ctx context.Context, ip string) (*Address, error)
- func (c *Client) Next(ctx context.Context, page *Page) (*Page, error)
- func (c *Client) NextZones(ctx context.Context, page *ZonePage) (*ZonePage, error)
- func (c *Client) Query(ctx context.Context, query string, opts ...QueryOption) (*Page, error)
- func (c *Client) QueryCombined(ctx context.Context, q Combined, opts ...QueryOption) (*Page, error)
- func (c *Client) RateLimits() []RateLimit
- func (c *Client) Zone(ctx context.Context, name string) (*ZoneInfo, error)
- func (c *Client) Zones(ctx context.Context, by ZonesBy, value string, opts ...QueryOption) (*ZonePage, error)
- type Combined
- type Count
- type DNSKey
- type DS
- type ListedZone
- type NSSet
- type Nameserver
- type NameserverAddress
- type Option
- type Page
- type QueryOption
- type RateLimit
- type RateLimitError
- type Record
- type Refusal
- type SOA
- type Zone
- type ZoneInfo
- type ZonePage
- type ZonesBy
Examples ¶
Constants ¶
const DefaultBaseURL = "https://activedns.net"
DefaultBaseURL is the server used unless WithBaseURL is given.
const Version = "0.3.0"
Version is the SDK version. It is sent in the User-Agent.
Variables ¶
var ErrNoAppName = errors.New(`activedns: app name is required, as in NewClient("mytool/1.0")`)
ErrNoAppName is returned by NewClient when app is empty.
var ErrNoMore = errors.New("activedns: no more pages")
ErrNoMore is returned by Client.Next when the page it is given is the last one of the result.
var ErrNoToken = errors.New("activedns: no token: none is built in and WithToken was not given")
ErrNoToken is returned by NewClient when there is no token to use: the build has none built in, and WithToken was not given or was empty.
Functions ¶
func IsForbidden ¶
IsForbidden reports whether err is a 403: the token does not allow the query, because of its type, the width of a network, a wildcard or the cursor. Other queries are unaffected.
func IsUnauthorized ¶
IsUnauthorized reports whether err is a 401: the token is unknown or revoked. After a 401 the Client sends no further requests, and every later call returns the same error.
Types ¶
type Address ¶ added in v0.3.0
type Address struct {
IP string `json:"ip"`
// Nameservers are the names at the address, the most recently seen first,
// up to 100. Names counts them all.
Nameservers []AddressNameserver `json:"nameservers,omitempty"`
Names Count `json:"names"`
// Refused counts the zones the address would not answer for. They are
// listed by [ByRefusedBy].
Refused Count `json:"refused"`
}
Address is the answer of Client.Nameservers.
type AddressNameserver ¶ added in v0.3.0
type AddressNameserver struct {
Name string `json:"name"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
// Origins are where it was seen, as NameserverAddress.Origins.
Origins []string `json:"origins,omitempty"`
// Zones counts the zones that have it in an NS set.
Zones Count `json:"zones"`
}
AddressNameserver is a nameserver name seen at an Address.
type Alias ¶
type Alias struct {
Name string `json:"name"`
// Chain is the names from Name to Domain, in order.
Chain []string `json:"chain"`
}
Alias is a CNAME name that resolves to a Record's Domain.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is an ActiveDNS API client. It is safe for concurrent use.
Use one Client per program: request pacing and rate-limit state are kept per client.
func NewClient ¶
NewClient returns a Client.
app names the calling program and its version, such as "mytool/1.2". It is sent in the User-Agent of every request and must be printable ASCII.
NewClient returns ErrNoAppName if app is empty, ErrNoToken if there is no token to use, and an error if an option is invalid.
func (*Client) Nameservers ¶ added in v0.3.0
Nameservers returns the nameserver names seen at an IPv4 or IPv6 address, each with the number of zones that use it, and the number of zones the address refused to answer for.
func (*Client) Next ¶
Next returns the page after page: the same query, continued where page ended. It returns ErrNoMore when page is the last one (Page.HasMore is false), and fails with a 403 (IsForbidden) past the depth the token may page to.
Example ¶
A query returns one page. Next fetches the one after it, for as long as the caller wants more.
package main
import (
"context"
"errors"
"fmt"
"log"
activedns "github.com/activedns/activedns-go"
)
func main() {
client, err := activedns.NewClient("mytool/1.0")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
page, err := client.Query(ctx, "*.example.com")
for fetched := 0; err == nil; {
for _, record := range page.Records {
fmt.Println(record.Domain, record.IPAddress)
}
fetched += len(page.Records)
if !page.HasMore || fetched >= 500 {
fmt.Printf("%d of %d records\n", fetched, page.Count)
break
}
page, err = client.Next(ctx, page)
}
var limit *activedns.RateLimitError
switch {
case errors.As(err, &limit):
log.Fatalf("rate limit %q reached, back at %s", limit.Exceeded, limit.RetryAt)
case err != nil:
log.Fatal(err)
}
}
Output:
func (*Client) NextZones ¶ added in v0.3.0
NextZones returns the page after page: the same request, continued where page ended. It returns ErrNoMore when page is the last one, and fails with a 403 (IsForbidden) past the depth the token may page to.
func (*Client) Query ¶
Query runs a query and returns one page of results. The package documentation lists the forms a query can take.
func (*Client) QueryCombined ¶
QueryCombined runs a combined query and returns one page of results. At least one field of q must be set. The token must allow combined queries.
func (*Client) RateLimits ¶
RateLimits returns the rate limits reported by the most recent response, or nil if it reported none.
func (*Client) Zone ¶ added in v0.3.0
Zone returns what is known of the zone that name is in: its NS sets, nameservers and their addresses, SOA, DS and DNSKEY records, and the servers that refused it. Beside each value is the number of zones that share it; Client.Zones lists them.
name is a domain name, not a wildcard: the zone of *.example.com is the zone of example.com. ZoneInfo.Zone is nil when no zone is known for it.
func (*Client) Zones ¶ added in v0.3.0
func (c *Client) Zones(ctx context.Context, by ZonesBy, value string, opts ...QueryOption) (*ZonePage, error)
Zones returns one page of the zones that share a value: a nameserver, an NS set, an SOA contact or primary, a DNSKEY, the parent they are delegated by, or a server that refused them. WithPageSize and WithCursor apply as they do to Client.Query; Client.NextZones continues from a page.
Example ¶
Zone returns what is known of the zone of a name. Zones lists the zones that share one of its values, a page at a time.
package main
import (
"context"
"fmt"
"log"
"time"
activedns "github.com/activedns/activedns-go"
)
func main() {
client, err := activedns.NewClient("mytool/1.0")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
info, err := client.Zone(ctx, "www.example.com")
if err != nil {
log.Fatal(err)
}
if info.Zone == nil || len(info.Zone.Nameservers) == 0 {
return
}
ns := info.Zone.Nameservers[0]
fmt.Println(ns.Name, "is in", ns.Zones.N, "zones")
page, err := client.Zones(ctx, activedns.ByNameserver, ns.Name, activedns.WithPageSize(20))
for err == nil {
for _, zone := range page.Zones {
fmt.Println(zone.Name, zone.LastSeen.Format(time.DateOnly))
}
if !page.HasMore {
break
}
page, err = client.NextZones(ctx, page)
}
if err != nil {
log.Fatal(err)
}
}
Output:
type Combined ¶
type Combined struct {
Domain string // a domain, or a leading wildcard such as "*.example.com"
IP string // an address, or a network in CIDR notation
ASN int // an AS number; 0 for none
}
Combined is a query for records that match every field that is set.
type Count ¶ added in v0.3.0
Count is how many zones share a value. N stops at 1000 for the values of a zone and at 10,000 for the size of a list; Capped is then true and N is a lower bound. A few zones is a lead, thousands is a provider.
type DNSKey ¶ added in v0.3.0
type DNSKey struct {
KeyTag int `json:"key_tag"`
Flags int `json:"flags"`
Algorithm int `json:"algorithm"`
// Fingerprint is the SHA-256 of the algorithm and the key, in hex. It is
// the value that finds the zones with the key: see [ByDNSKey].
Fingerprint string `json:"fingerprint"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
// Zones counts the zones with this key.
Zones Count `json:"zones"`
}
DNSKey is a DNSSEC key of a zone.
type DS ¶ added in v0.3.0
type DS struct {
KeyTag int `json:"key_tag"`
Algorithm int `json:"algorithm"`
DigestType int `json:"digest_type"`
// Digest is in hex.
Digest string `json:"digest"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
}
DS is a delegation signer record of a zone.
type ListedZone ¶ added in v0.3.0
type ListedZone struct {
Name string `json:"name"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
// Reason is why the server would not answer, for [ByRefusedBy].
Reason string `json:"reason,omitempty"`
}
ListedZone is a zone in a ZonePage, the most recently seen first.
type NSSet ¶ added in v0.3.0
type NSSet struct {
NS []string `json:"ns"`
// Origin is "parent" (the delegation, from a server of From) or "apex"
// (from the zone's own servers).
Origin string `json:"origin"`
From string `json:"from,omitempty"`
// Server is the address of the server that gave it last.
Server string `json:"server,omitempty"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
// Zones counts the zones with exactly this set.
Zones Count `json:"zones"`
}
NSSet is a set of nameservers seen for a zone.
type Nameserver ¶ added in v0.3.0
type Nameserver struct {
Name string `json:"name"`
// Provider is the registrable domain of the name: whose nameserver it is.
Provider string `json:"provider,omitempty"`
Addresses []NameserverAddress `json:"addresses,omitempty"`
// Zones counts the zones that have it in an NS set.
Zones Count `json:"zones"`
}
Nameserver is a nameserver of a zone.
type NameserverAddress ¶ added in v0.3.0
type NameserverAddress struct {
IP string `json:"ip"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
// Origins are where it was seen: "glue" or "additional" (in a referral)
// and "answer" (the resolver's lookup of the name).
Origins []string `json:"origins,omitempty"`
// Names counts the nameserver names at this address.
Names Count `json:"names"`
}
NameserverAddress is an address of a Nameserver.
type Option ¶
type Option func(*config)
Option configures a Client in NewClient.
func WithBaseURL ¶
WithBaseURL sets the server, as scheme and host: "http://localhost:8080". The default is DefaultBaseURL.
func WithHTTPClient ¶
WithHTTPClient sets the HTTP client. The default has a 60-second timeout.
func WithMaxConcurrent ¶
WithMaxConcurrent sets how many requests may be in flight at once. The default is 1. It does not raise the server's rate limit.
func WithMaxRetries ¶
WithMaxRetries sets how many times a request is retried after a 429, 502, 503, 504 or connection failure. The default is 4; 0 disables retries.
func WithMaxWait ¶
WithMaxWait sets the longest pause the client sleeps through before a retry. The default is 30 seconds. When the server asks for a longer wait, the call returns a *RateLimitError instead.
type Page ¶
type Page struct {
Records []Record `json:"records"`
// Count is the number of records that match the query in all, not the
// number in this page. It is an estimate when CountEstimated is set,
// and a lower bound when CountCapped is set.
Count int `json:"count"`
CountEstimated bool `json:"count_estimated,omitempty"`
CountCapped bool `json:"count_capped,omitempty"`
// HasMore is true when there are records after this page. NextCursor
// is then the offset of the first of them.
HasMore bool `json:"has_more"`
NextCursor int `json:"next_cursor"`
// TimedOut is true when the server gave up on the search. Records may
// be incomplete. The client does not retry such a query.
TimedOut bool `json:"timed_out,omitempty"`
// Query is the query as the server normalised it. QueryType is how it
// was classified: "domain", "wildcard_domain", "ip", "cidr", "asn" or
// "combined".
QueryType string `json:"query_type"`
Query string `json:"query"`
// contains filtered or unexported fields
}
Page is one page of results. Count and HasMore say whether there is more than this page; Client.Next fetches the next one.
type QueryOption ¶
QueryOption adjusts a single query.
func WithCursor ¶
func WithCursor(n int) QueryOption
WithCursor sets the offset of the first record returned. To continue from a Page, Client.Next is simpler.
func WithPageSize ¶
func WithPageSize(n int) QueryOption
WithPageSize sets the most records returned per page. The default is 100; the token's policy caps it, without an error: a larger request returns the token's maximum.
type RateLimit ¶
type RateLimit struct {
// Name identifies the limit. The ActiveDNS API uses "address",
// "network" and "token". Empty if the server gave none.
Name string
// Limit is how many requests the limit allows at once, or -1 if not
// reported.
Limit int64
// Remaining is how many requests can be made now.
Remaining int64
// Reset is when more requests become available. Zero if not reported.
Reset time.Time
}
RateLimit is a rate limit as the server reported it in the rate-limit headers of a response.
type RateLimitError ¶
type RateLimitError struct {
// Exceeded names the limit: "address" (the client's network address),
// "network" (the network around it) or "token". Empty if the server
// did not say.
Exceeded string
// RetryAt is when the limit allows another request. Zero if unknown.
RetryAt time.Time
// Message is the server's error message.
Message string
// Local is true when the request was not sent, because an earlier
// response had already reported the limit.
Local bool
}
RateLimitError is returned when a rate limit is reached and the client did not wait it out: the wait was longer than WithMaxWait allows, or the retries were used up.
func (*RateLimitError) Error ¶
func (e *RateLimitError) Error() string
type Record ¶
type Record struct {
ID int64 `json:"id"`
IPAddress string `json:"ip_address"`
// Domain is the name that holds the address. For a name that is a
// CNAME, it is the name at the end of the chain; see Aliases.
Domain string `json:"domain"`
// Observed is the most recent observation, FirstSeen the first.
Observed time.Time `json:"observed"`
FirstSeen time.Time `json:"first_seen"`
// IPVersion is 4 or 6.
IPVersion int `json:"ip_version"`
// ASN and Country are those of the address. Zero when unknown.
ASN int `json:"asn,omitempty"`
Country string `json:"country,omitempty"`
// Aliases lists the CNAME names that matched a name query and lead to
// Domain. Empty when Domain itself matched.
Aliases []Alias `json:"aliases,omitempty"`
// KnownAs lists some of the CNAME names that point at Domain. It is
// set on address, network and AS-number queries. KnownAsTotal is how
// many there are, capped at 1000.
KnownAs []Alias `json:"known_as,omitempty"`
KnownAsTotal int `json:"known_as_total,omitempty"`
}
Record is one observation: a name that resolved to an address.
type Refusal ¶ added in v0.3.0
type Refusal struct {
Server string `json:"server"`
// Reason is refused, servfail, notauth or upward.
Reason string `json:"reason"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
}
Refusal is a server that would not answer for a zone.
type SOA ¶ added in v0.3.0
type SOA struct {
Primary string `json:"primary"`
// Contact is the RNAME as it is in DNS (hostmaster.example.com), Email
// the address it stands for (hostmaster@example.com).
Contact string `json:"contact"`
Email string `json:"email,omitempty"`
Serial int64 `json:"serial"`
Refresh int `json:"refresh"`
Retry int `json:"retry"`
Expire int `json:"expire"`
Minimum int `json:"minimum"`
// Since is when these values were first seen without others between.
Since time.Time `json:"since"`
LastSeen time.Time `json:"last_seen"`
// PrimaryZones counts the zones with this primary, ContactZones those
// with this contact.
PrimaryZones Count `json:"primary_zones"`
ContactZones Count `json:"contact_zones"`
}
SOA is the start-of-authority record of a zone.
type Zone ¶ added in v0.3.0
type Zone struct {
Name string `json:"name"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
// Parent is the zone that gave the newest delegation of this one.
Parent string `json:"parent,omitempty"`
// Children counts the zones this one delegates.
Children *Count `json:"children,omitempty"`
// NSSets are the NS sets seen for the zone, from its parent and from its
// own servers, newest first.
NSSets []NSSet `json:"ns_sets,omitempty"`
// Nameservers are the names in NSSets, each once, with their addresses.
Nameservers []Nameserver `json:"nameservers,omitempty"`
SOA *SOA `json:"soa,omitempty"`
DS []DS `json:"ds,omitempty"`
DNSKeys []DNSKey `json:"dnskeys,omitempty"`
// Refusals are the servers that would not answer for the zone.
Refusals []Refusal `json:"refusals,omitempty"`
}
Zone is what is known of a DNS zone.
type ZoneInfo ¶ added in v0.3.0
type ZoneInfo struct {
// Query is the name asked for, as the server normalised it.
Query string `json:"query"`
// QueryIsNameserver is true when the name is itself a nameserver: in an
// NS set, named as SOA primary by a zone, or with an address. The zone
// says nothing about it (the zone of ns1.example.net is example.net), so
// [Client.Nameservers] may have an answer for it too.
QueryIsNameserver bool `json:"query_is_nameserver,omitempty"`
// Zone is the zone the name is in. Nil when none is known.
Zone *Zone `json:"zone"`
// RegistrableDomains maps the zone and its parent to their registrable
// domains. A zone that is a public suffix (pl, co.uk) has none.
RegistrableDomains map[string]string `json:"registrable_domains,omitempty"`
}
ZoneInfo is the answer of Client.Zone.
type ZonePage ¶ added in v0.3.0
type ZonePage struct {
Zones []ListedZone `json:"zones"`
// By and Value are the request as the server understood it. Subject is
// the value shared when it is not Value itself: the SOA contact, as an
// address, for [BySOAContactOf].
By ZonesBy `json:"by"`
Value string `json:"value"`
Subject string `json:"subject,omitempty"`
// Count is the number of zones in all, not in this page. It stops at
// 10,000 (Count.Capped).
Count Count `json:"count"`
// RegistrableDomains maps zones to their registrable domains.
RegistrableDomains map[string]string `json:"registrable_domains,omitempty"`
// HasMore is true when there are zones after this page. NextCursor is
// then the offset of the first of them.
HasMore bool `json:"has_more"`
NextCursor int `json:"next_cursor"`
// contains filtered or unexported fields
}
ZonePage is one page of the zones that share a value. ZonePage.HasMore and Client.NextZones work as Page.HasMore and Client.Next do.
type ZonesBy ¶ added in v0.3.0
type ZonesBy string
ZonesBy is what the zones of a Client.Zones list share.
const ( // ByNameserver lists the zones with a nameserver in an NS set. The value // is the name of the nameserver. ByNameserver ZonesBy = "nameserver" // ByNSSet lists the zones with exactly an NS set. The value is its // names joined by commas. ByNSSet ZonesBy = "ns_set" // BySOAContactOf lists the zones with the SOA contact of a zone. The // value is that zone, not the address. BySOAContactOf ZonesBy = "soa_contact_of" // BySOAPrimary lists the zones with an SOA primary. The value is its // name. BySOAPrimary ZonesBy = "soa_primary" // ByDNSKey lists the zones with a DNSKEY. The value is its fingerprint: // 64 hex digits, see [DNSKey]. ByDNSKey ZonesBy = "dnskey" // ByChildren lists the zones that a zone delegates. The value is that // zone. ByChildren ZonesBy = "children" // ByRefusedBy lists the zones that a server would not answer for. The // value is the address of the server. ByRefusedBy ZonesBy = "refused_by" )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
example
|
|
|
combined
command
Combined searches for records that match a domain and a network and an AS number at once: any two or three of them.
|
Combined searches for records that match a domain and a network and an AS number at once: any two or three of them. |
|
query
command
Query runs one search and prints the first page of the answer.
|
Query runs one search and prints the first page of the answer. |
|
subdomains
command
Subdomains lists the names known under a domain, with the addresses each one has resolved to.
|
Subdomains lists the names known under a domain, with the addresses each one has resolved to. |
|
zone
command
Zone prints what is known of the zone of a name, and lists a few of the zones that share its first nameserver.
|
Zone prints what is known of the zone of a name, and lists a few of the zones that share its first nameserver. |