netattr

package
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: BSD-3-Clause Imports: 15 Imported by: 0

Documentation

Overview

Package netattr attributes an IP address to a network: the special-purpose registry it belongs to, or the cloud provider and region that announces it.

The two halves of this package have deliberately different characters. The special-purpose ranges are transcribed from the IANA registries: they are small, they change perhaps once a decade, and they are authoritative, so they are embedded and a lookup against them is exact. Provider ranges are none of those things — they change daily and only the providers know them — so they are fetched from each provider's own published file and cached, never guessed. An address this package cannot attribute is reported as unattributed rather than as "not in a cloud", because a partial answer must not be dressed up as a complete one.

Index

Constants

View Source
const CacheTTL = 7 * 24 * time.Hour

CacheTTL is how long a downloaded range file is reused.

Seven days is a compromise between accuracy and courtesy. Operators revise these files daily, but a prefix that moved yesterday is still almost always announced by the same operator, so the cost of week-old data is a slightly stale region rather than a wrong provider. The benefit is that auditing a portfolio does not pull several megabytes from four operators every run.

Variables

This section is empty.

Functions

func CacheDir

func CacheDir() (string, error)

CacheDir returns the directory range files are cached in.

func IsGlobalUnicast

func IsGlobalUnicast(addr netip.Addr) bool

IsGlobalUnicast reports whether an address is ordinary routable space.

func JurisdictionOf

func JurisdictionOf(region string) string

JurisdictionOf returns the ISO 3166-1 alpha-2 country for a region identifier, or the empty string when it is not known.

Types

type Attribution

type Attribution struct {
	// Address is the address looked up.
	Address netip.Addr
	// Special is the special-purpose registry entry, when the address is in
	// one.
	Special *SpecialRange
	// Provider is the operator announcing the address, when known.
	Provider string
	// Source cites where the provider's ranges came from.
	Source string
	// Region is the operator's region identifier, when published.
	Region string
	// Jurisdiction is the ISO 3166-1 alpha-2 country the region sits in, when
	// the mapping is known. Empty means unknown, never "assume home".
	Jurisdiction string
	// Prefix is the announced prefix that matched.
	Prefix netip.Prefix
}

Attribution is what could be established about an address.

func (Attribution) Attributed

func (a Attribution) Attributed() bool

Attributed reports whether anything at all was established about the address. An unattributed address is not evidence that the host is self-hosted; it is evidence that this tool does not know, and the rules must treat it that way.

type Category

type Category string

Category classifies a special-purpose range by what its appearance in public DNS actually means.

const (
	// CategoryPrivate is RFC 1918 and IPv6 unique-local space: addresses that
	// are meaningful only inside the organisation's own network.
	CategoryPrivate Category = "private"
	// CategoryCGNAT is RFC 6598 shared address space, used between a carrier
	// and its subscribers.
	CategoryCGNAT Category = "cgnat"
	// CategoryLoopback is the host itself.
	CategoryLoopback Category = "loopback"
	// CategoryLinkLocal is address space valid only on a single link.
	CategoryLinkLocal Category = "link-local"
	// CategoryDocumentation is space reserved for examples, which is never
	// routable and is usually a placeholder somebody forgot to replace.
	CategoryDocumentation Category = "documentation"
	// CategoryReserved is space reserved or unallocated by IANA.
	CategoryReserved Category = "reserved"
	// CategoryUnspecified is the all-zeroes address.
	CategoryUnspecified Category = "unspecified"
	// CategorySpecial is other special-purpose space that is neither private
	// nor a placeholder: multicast, broadcast, protocol assignments.
	CategorySpecial Category = "special"
)

The categories a special-purpose range may carry.

func (Category) DisclosesInternalAddressing

func (c Category) DisclosesInternalAddressing() bool

DisclosesInternalAddressing reports whether a category, published in the public DNS, hands an outsider information about the organisation's internal network.

Loopback and the unspecified address are excluded on purpose. Both are widely and deliberately used to null-route a name that must exist but must not resolve anywhere, so treating them as a leak would report a common defensive practice as a defect. They are still worth surfacing, but as a weaker claim.

type Loader added in v1.2.0

type Loader struct {
	// HTTP is the egress range files are fetched through. Nil uses the
	// library default client.
	HTTP d.Doer
	// Store, when non-nil, persists downloads between runs.
	Store RangeStore
	// TTL overrides how long a cached file is reused. Zero uses CacheTTL.
	TTL time.Duration
	// contains filtered or unexported fields
}

Loader fetches and memoises provider ranges.

It replaces what used to be package-level state. The distinction matters for more than tidiness: the memo is populated by fetches made through a specific HTTP client, and an embedding consumer may have granted that client access to third-party endpoints under one authorisation and not another. A process-wide memo would let one assessment serve another the results of an endpoint the second was never permitted to contact — a consent leak that no scope guard downstream could detect, because no request would be made.

func NewLoader added in v1.2.0

func NewLoader(hc d.Doer, store RangeStore) *Loader

NewLoader builds a loader over an HTTP client and an optional store.

func (*Loader) Load added in v1.2.0

func (l *Loader) Load(ctx context.Context) (Set, error)

Load returns the provider ranges, fetching and caching them as needed.

The result is memoised on the loader, so a portfolio audit of a hundred domains pays for the download once while two assessments under different authorisations remain independent.

func (*Loader) Reset added in v1.2.0

func (l *Loader) Reset()

Reset clears the loader's memo. Tests use it; nothing else should.

type Provider

type Provider struct {
	// Name is the operator.
	Name string
	// Source is the URL the ranges were published at, retained so a finding
	// can cite where the attribution came from.
	Source string
	// Ranges are the announced prefixes.
	Ranges []ProviderRange
}

Provider is a cloud or CDN operator whose announced ranges are known.

type ProviderRange

type ProviderRange struct {
	Prefix netip.Prefix
	// Region is the operator's own region identifier, e.g. "eu-west-1". It is
	// empty when the operator does not publish one.
	Region string
}

ProviderRange is one announced prefix and what is known about where it is.

type RangeStore added in v1.2.0

type RangeStore interface {
	// Get returns cached content and when it was obtained. The boolean
	// reports whether anything was found, regardless of age: freshness is the
	// loader's judgement, because only it knows whether a stale entry is
	// preferable to no answer at all.
	Get(ctx context.Context, url string) (data []byte, at time.Time, ok bool)
	// Put records content obtained now.
	Put(ctx context.Context, url string, data []byte, at time.Time) error
}

RangeStore is a durable cache of downloaded provider range files.

It is an interface so that an embedding consumer can back it with its own storage and share one download across many assessments. Without it, each assessment would re-fetch several megabytes from four operators, which is precisely the inconsiderate behaviour this tool must avoid.

Implementations must be safe for concurrent use.

type Set

type Set struct {
	// Providers are the operators whose ranges loaded successfully.
	Providers []Provider
	// Failed names the sources that could not be loaded, so a caller can say
	// "coverage was incomplete" rather than "not in a cloud".
	Failed []string
	// Stale names the sources served from a cache entry older than CacheTTL,
	// with the age, because the operator's endpoint could not be reached.
	// Serving week-old data is defensible; doing so without saying is not.
	Stale []string
	// Provenance records where each operator's ranges came from and when, so a
	// consumer diffing two runs can tell a moved host from refreshed data.
	Provenance []SourceProvenance
	// Fetched is when the data was obtained, which is what lets a reader judge
	// how stale an attribution may be.
	Fetched time.Time
}

Set is a loaded collection of provider ranges.

func (Set) Complete

func (s Set) Complete() bool

Complete reports whether every source loaded. When false, an address with no provider match may simply belong to an operator whose ranges are missing.

func (Set) Lookup

func (s Set) Lookup(addr netip.Addr) Attribution

Lookup attributes an address.

Special-purpose space is checked first and short-circuits: an RFC 1918 address cannot also be an operator's announced range, and a provider file that claimed otherwise would be wrong.

type SourceProvenance

type SourceProvenance struct {
	// Provider is the operator the ranges describe.
	Provider string
	// URL is the endpoint the data was obtained from, which may be a fallback
	// rather than the preferred one.
	URL string
	// Fetched is when the data was obtained, not when it was used.
	Fetched time.Time
}

SourceProvenance records where one operator's ranges came from and when.

Attribution is derived from data this tool fetches, so an attribution can change without the audited domain changing at all. Spec 013 diffs records between runs and requires that false drift be avoidable, so a consumer needs to be able to tell "the host moved" from "our data changed". That is only possible if the data's origin and age travel with the result.

type SpecialRange

type SpecialRange struct {
	// Prefix is the range.
	Prefix netip.Prefix
	// Name is the registry's name for it.
	Name string
	// Category is what its presence in public DNS means.
	Category Category
	// Reference cites the RFC that reserved it.
	Reference string
}

SpecialRange is one entry from the IANA special-purpose address registries.

func LookupSpecial

func LookupSpecial(addr netip.Addr) (SpecialRange, bool)

LookupSpecial returns the special-purpose range containing the address.

The most specific match wins, because the registries nest: 192.0.2.0/24 sits inside no other entry today, but 100::/64 sits inside space that later registry revisions may cover, and a first-match implementation would report whichever entry happened to be listed first.

Jump to

Keyboard shortcuts

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