libdns

package module
v1.1.1 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2025 License: MIT Imports: 6 Imported by: 501

README

libdns - Universal DNS provider APIs for Go

Go Reference

libdns is a collection of free-range DNS provider client implementations written in Go! With libdns packages, your Go program can manage DNS records across any supported providers. A "provider" is a service or program that manages a DNS zone.

This repository defines the core APIs that provider packages should implement. They are small and idiomatic Go interfaces with well-defined semantics for managing DNS records.

The interfaces include:

See full godoc for detailed information.

Implementations

This package only defines standardized APIs described above. To actually manipulate DNS records/zones, you will need a package specific to your provider that implements these interfaces.

You can choose from over 80 packages at https://github.com/libdns.

Example

To work with DNS records managed by Cloudflare, for example, we can use libdns/cloudflare:

import (
	"github.com/libdns/cloudflare"
	"github.com/libdns/libdns"
)

ctx := context.TODO()

zone := "example.com."

// configure the DNS provider (choose any from github.com/libdns)
provider := cloudflare.Provider{APIToken: "topsecret"}

// list records
recs, err := provider.GetRecords(ctx, zone)

// create records (AppendRecords is similar, with different semantics)
newRecs, err := provider.SetRecords(ctx, zone, []libdns.Record{
	libdns.Address{
		Name:  "@",
		Value: netip.MustParseAddr("1.2.3.4"),
	},
})

// delete records
deletedRecs, err := provider.DeleteRecords(ctx, zone, []libdns.Record{
	libdns.TXT{
		Name: "subdomain",
		Text: "txt value I want to delete"
	},
})

// no matter which provider you use, the code stays the same!
// (some providers have caveats; see their package documentation)

Implementing new provider packages

Provider packages are 100% written and maintained by the community! Collectively, we as members of the community each maintain the packages for providers we personally use.

Instructions for adding new libdns packages are on this repo's wiki. Please feel free to contribute a package for your provider!

Similar projects

OctoDNS is a suite of tools written in Python for managing DNS. However, its approach is a bit heavy-handed when all you need are small, incremental changes to a zone:

WARNING: OctoDNS assumes ownership of any domain you point it to. When you tell it to act it will do whatever is necessary to try and match up states including deleting any unexpected records. Be careful when playing around with OctoDNS.

This is incredibly useful when you are maintaining your own zone file, but risky when you just need incremental changes.

StackExchange/dnscontrol is written in Go, but is similar to OctoDNS in that it tends to obliterate your entire zone and replace it with your input. Again, this is very useful if you are maintaining your own master list of records, but doesn't do well for simply adding or removing records.

go-acme/lego supports many DNS providers, but their APIs are only capable of setting and deleting TXT records for ACME challenges.

miekg/dns is a comprehensive, low-level DNS library for Go programs. It is well-maintained and extremely thorough, but also too low-level to be effective for our use cases.

libdns takes inspiration from the above projects but aims for a more generally-useful set of high-level APIs that homogenize pretty well across providers. In contrast to most of the above projects, libdns can add, set, delete, and get arbitrary records from a zone without obliterating it (although syncing up an entire zone is also possible!). Its APIs also include context so long-running calls can be cancelled early, for example to accommodate on-line config changes downstream. libdns interfaces are also smaller and more composable. Additionally, libdns can grow to support a nearly infinite number of DNS providers without added bloat, because each provider implementation is a separate Go module, which keeps your builds lean and fast.

In summary, the goal is that libdns providers can do what the above libraries/tools can do, but with more flexibility: they can create and delete TXT records for ACME challenges, they can replace entire zones, but they can also do incremental changes or simply read records.

Whatever libdns is used for with your DNS zone, it is presumed that only your libdns code is manipulating that (part of your) zone. This package does not provide synchronization primitives, but your own code can do that if necessary.

Realistically, libdns should enable most common record manipulations, but may not be able to fit absolutely 100% of all possibilities with DNS in a provider-agnostic way. That is probably OK; and given the wide varieties in DNS record types and provider APIs, it would be unreasonable to expect otherwise. Our goal is 100% fulfillment of ~99% of use cases / user requirements, not 100% fulfillment of 100% of use cases.

Documentation

Overview

Package libdns defines core interfaces that should be implemented by packages that interact with DNS provider clients. These interfaces are small and idiomatic Go interfaces with well-defined semantics for the purposes of reading and manipulating DNS records using DNS provider APIs.

This documentation uses the definitions for terms from RFC 9499: https://datatracker.ietf.org/doc/html/rfc9499

This package represents records with the Record interface, which is any type that can transform itself into the RR struct. This interface is implemented by the various record abstractions this package offers: RR structs, where the data is serialized as a single opaque string as if in a zone file, being a type-agnostic Resource Record (that is, a name, type, class, TTL, and data); and individual RR-type structures, where the data is parsed into its separate fields for easier manipulation by Go programs (for example: SRV, TXT, and ServiceBinding types). This hybrid design grants great flexibility for both DNS provider packages and consumer Go programs.

Record values should not be primitvely compared (==) unless they are RR, because other struct types contain maps, for which equality is not defined; additionally, some packages may attach custom data to each RR struct-type's `ProviderData` field, whose values might not be comparable either. The `ProviderData` field is not portable across providers, or possibly even zones. Because it is not portable, and we want to ensure that RR structs remain both portable and comparable, the `RR()` method does not preserve `ProviderData` in its return value. Users of libdns packages should check the documentation of provider packages, as some may use the `ProviderData` field to reduce API calls / increase effiency. But implementations must never rely on `ProviderData` for correctness if possible (and should document clearly otherwise).

Implementations of the libdns interfaces should accept as input any Record value, and should return as output the concrete struct types that implement the Record interface (i.e. Address, TXT, ServiceBinding, etc). This is important to ensure the provider libraries are robust and also predictable: callers can reliably type-switch on the output to immediately access structured data about each record without the possibility of errors. Returned values should be of types defined by this package to make type-assertions reliable.

Records are described independently of any particular zone, a convention that grants records portability across zones. As such, record names are partially qualified, i.e. relative to the zone. For example, a record called “sub” in zone “example.com.” represents a fully-qualified domain name (FQDN) of “sub.example.com.”. Implementations should expect that input records conform to this standard, while also ensuring that output records do; adjustments to record names may need to be made before or after provider API calls, for example, to maintain consistency with all other libdns packages. Helper functions are available in this package to convert between relative and absolute names; see RelativeName and AbsoluteName.

Although zone names are a required input, libdns does not coerce any particular representation of DNS zones; only records. Since zone name and records are separate inputs in libdns interfaces, it is up to the caller to maintain the pairing between a zone's name and its records.

All interface implementations must be safe for concurrent/parallel use, meaning 1) no data races, and 2) simultaneous method calls must result in either both their expected outcomes or an error. For example, if libdns.RecordAppender.AppendRecords is called simultaneously, and two API requests are made to the provider at the same time, the result of both requests must be visible after they both complete; if the provider does not synchronize the writing of the zone file and one request overwrites the other, then the client implementation must take care to synchronize on behalf of the incompetent provider. This synchronization need not be global; for example: the scope of synchronization might only need to be within the same zone, allowing multiple requests at once as long as all of them are for different zone. (Exact logic depends on the provider.)

Some service providers APIs may enforce rate limits or have sporadic errors. It is generally expected that libdns provider packages implement basic retry logic (e.g. retry up to 3-5 times with backoff in the event of a connection error or some HTTP error that may be recoverable, including 5xx or 429s) when it is safe to do so. Retrying/recovering from errors should not add substantial latency, though. If it will take longer than a couple seconds, best to return an error.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AbsoluteName added in v0.2.0

func AbsoluteName(name, zone string) string

AbsoluteName makes name into a fully-qualified domain name (FQDN) by prepending it to zone and tidying up the dots. For example, an input of name “sub” and zone “example.com.” will return “sub.example.com.”. If the name ends with a dot, it will be returned as the FQDN.

Using “@” as the name is the recommended way to represent the root of the zone; however, unlike the Record struct, using the empty string "" for the name *is* permitted here, and will be treated identically to “@”.

In the name already has a trailing dot, it is returned as-is. This is similar to the behavior of path/filepath.Abs, and means that AbsoluteName is idempotent, so it is safe to call multiple times without first checking if the name is absolute or relative.

Example
fmt.Println(AbsoluteName("sub", "example.com."))
Output:

sub.example.com.

func RelativeName added in v0.2.0

func RelativeName(fqdn, zone string) string

RelativeName makes “fqdn” relative to “zone”. For example, for a FQDN of “sub.example.com” and a zone of “example.com.”, it returns “sub”.

If fqdn is the same as zone (and both are non-empty), “@” is returned.

If fqdn cannot be expressed relative to zone, the input fqdn is returned.

Example
fmt.Println(RelativeName("sub.example.com.", "example.com."))
Output:

sub

Types

type Address added in v1.0.0

type Address struct {
	Name string
	TTL  time.Duration
	IP   netip.Addr

	// Optional custom data associated with the provider serving this record.
	// See the package godoc for important details on this field.
	ProviderData any
}

Address represents a parsed A-type or AAAA-type record, which associates a name with an IPv4 or IPv6 address respectively. This is typically how to "point a domain to your server."

Since A and AAAA are semantically identical, with the exception of the bit length of the IP address in the data field, these record types are combined for ease of use in Go programs, which supports both address sizes, to help simplify code.

func (Address) RR added in v1.0.0

func (a Address) RR() RR

type AtomicErr added in v1.1.0

type AtomicErr error

AtomicErr should be returned as the error when a method errors atomically. When this error type is returned, the caller can know that their zone remains in a consistent state despite an error.

type CAA added in v1.0.0

type CAA struct {
	Name  string
	TTL   time.Duration
	Flags uint8 // As of March 2025, the only valid values are 0 and 128.
	Tag   string
	Value string

	// Optional custom data associated with the provider serving this record.
	// See the package godoc for important details on this field.
	ProviderData any
}

CAA represents a parsed CAA-type record, which is used to specify which PKIX certificate authorities are allowed to issue certificates for a domain. See also the registry of flags and tags.

func (CAA) RR added in v1.0.0

func (c CAA) RR() RR

type CNAME added in v1.0.0

type CNAME struct {
	Name   string
	TTL    time.Duration
	Target string

	// Optional custom data associated with the provider serving this record.
	// See the package godoc for important details on this field.
	ProviderData any
}

CNAME represents a CNAME-type record, which delegates authority to other names.

func (CNAME) RR added in v1.0.0

func (c CNAME) RR() RR

type MX added in v1.0.0

type MX struct {
	Name       string
	TTL        time.Duration
	Preference uint16 // Lower values indicate that clients should prefer this server. This field is similar to the “Priority” field in SRV records.
	Target     string // The hostname of the mail server

	// Optional custom data associated with the provider serving this record.
	// See the package godoc for important details on this field.
	ProviderData any
}

MX represents a parsed MX-type record, which is used to specify the hostnames of the servers that accept mail for a domain.

func (MX) RR added in v1.0.0

func (m MX) RR() RR

type NS added in v1.0.0

type NS struct {
	Name   string
	TTL    time.Duration
	Target string

	// Optional custom data associated with the provider serving this record.
	// See the package godoc for important details on this field.
	ProviderData any
}

NS represents a parsed NS-type record, which is used to specify the authoritative nameservers for a zone. It is strongly recommended to have at least two NS records for redundancy.

Note that the NS records present at the root level of a zone must match those delegated to by the parent zone. This means that changing the NS records for the root of a registered domain won't have any effect unless you also update the NS records with the domain registrar.

Also note that the DNS standards forbid removing the last NS record for a zone, so if you want to replace all NS records, you should add the new ones before removing the old ones.

func (NS) RR added in v1.0.0

func (n NS) RR() RR

type RR added in v1.0.0

type RR struct {
	// The name of the record. It is partially qualified, relative to the zone.
	// For the sake of consistency, use "@" to represent the root of the zone.
	// An empty name typically refers to the last-specified name in the zone
	// file, which is only determinable in specific contexts.
	//
	// (For the following examples, assume the zone is “example.com.”)
	//
	// Examples:
	//   - “www” (for “www.example.com.”)
	//   - “@” (for “example.com.”)
	//   - “subdomain” (for “subdomain.example.com.”)
	//   - “sub.subdomain” (for “sub.subdomain.example.com.”)
	//
	// Invalid:
	//   - “www.example.com.” (fully-qualified)
	//   - “example.net.” (fully-qualified)
	//   - "" (empty)
	//
	// Valid, but probably doesn't do what you want:
	//   - “www.example.net” (refers to “www.example.net.example.com.”)
	Name string `json:"name"`

	// The time-to-live of the record. This is represented in the DNS zone file as
	// an unsigned integral number of seconds, but is provided here as a
	// [time.Duration] for ease of use in Go code. Fractions of seconds will be
	// rounded down (truncated). A value of 0 means that the record should not be
	// cached. Some provider implementations may assume a default TTL from 0; to
	// avoid this, set TTL to a sub-second duration.
	//
	// Note that some providers may reject or silently increase TTLs that are below
	// a certain threshold, and that DNS resolvers may choose to ignore your TTL
	// settings, so it is recommended to not rely on the exact TTL value.
	TTL time.Duration `json:"ttl"`

	// The type of the record as an uppercase string. DNS provider packages are
	// encouraged to support as many of the most common record types as possible,
	// especially: A, AAAA, CNAME, TXT, HTTPS, and SRV.
	//
	// Other custom record types may be supported with implementation-defined
	// behavior.
	Type string `json:"type"`

	// The data (or "value") of the record. This field should be formatted in
	// the *unescaped* standard zone file syntax (technically, the "RDATA" field
	// as defined by RFC 1035 §5.1). Due to variances in escape sequences and
	// provider support, this field should not contain escapes. More concretely,
	// the following [libdns.Record]s
	//
	//  []libdns.TXT{
	//      {
	//          Name: "alpha",
	//          Text: `quotes " backslashes \000`,
	//      }, {
	//          Name: "beta",
	//          Text: "del: \x7F",
	//      },
	//  }
	//
	// should be equivalent to the following in zone file syntax:
	//
	//  alpha  0  IN  TXT  "quotes \" backslashes \\000"
	//  beta   0  IN  TXT  "del: \177"
	//
	// Implementations are not expected to support RFC 3597 “\#” escape
	// sequences, but may choose to do so if they wish.
	Data string `json:"data"`
}

RR represents a DNS Resource Record, which resembles how records are represented by DNS servers in zone files.

The fields in this struct are common to all RRs, with the data field being opaque; it has no particular meaning until it is parsed.

This type should NOT be returned by implementations of the libdns interfaces; in other words, methods such as GetRecords, AppendRecords, etc., should not return RR values. Instead, they should return the structs corresponding to the specific RR types (such as Address, TXT, etc). This provides consistency for callers who can then reliably type-switch or type-assert the output without the possibility for errors.

Implementations are permitted to define their own types that implement the RR interface, but this should only be done for provider-specific types. If you're instead wanting to use a general-purpose DNS RR type that is not yet supported by this package, please open an issue or PR to add it.

func (RR) Parse added in v1.0.0

func (r RR) Parse() (Record, error)

Parse returns a type-specific structure for this RR, if it is a known/supported type. Otherwise, it returns itself.

Callers will typically want to type-assert (or use a type switch on) the return value to extract values or manipulate it.

func (RR) RR added in v1.0.0

func (r RR) RR() RR

RR returns itself. This may be the case when trying to parse an RR type that is not (yet) supported/implemented by this package.

type Record

type Record interface {
	RR() RR
}

Record is any type that can reduce itself to the RR struct.

Primitive equality (“==”) between any two [Record]s is explicitly undefined; if implementations need to compare records, they should either define their own equality functions or compare the RR structs directly.

type RecordAppender

type RecordAppender interface {
	// AppendRecords creates the inputted records in the given zone and returns
	// the populated records that were created. It never changes existing records.
	//
	// Therefore, it makes little sense to use this method with CNAME-type
	// records since if there are no existing records with the same name, it
	// behaves the same as [libdns.RecordSetter.SetRecords], and if there are
	// existing records with the same name, it will either fail or leave the
	// zone in an invalid state.
	//
	// Implementations should return struct types defined by this package which
	// correspond with the specific RR-type (instead of the opaque [RR] struct).
	//
	// Implementations must honor context cancellation and be safe for concurrent
	// use.
	AppendRecords(ctx context.Context, zone string, recs []Record) ([]Record, error)
}

RecordAppender can non-destructively add new records to a DNS zone.

type RecordDeleter

type RecordDeleter interface {
	// DeleteRecords deletes the given records from the zone if they exist in the
	// zone and exactly match the input. If the input records do not exist in the
	// zone, they are silently ignored. DeleteRecords returns only the the records
	// that were deleted, and does not return any records that were provided in the
	// input but did not exist in the zone.
	//
	// DeleteRecords only deletes records from the zone that *exactly* match the
	// input records—that is, the name, type, TTL, and value all must be identical
	// to a record in the zone for it to be deleted.
	//
	// As a special case, you may leave any of the fields [libdns.Record.Type],
	// [libdns.Record.TTL], or [libdns.Record.Value] empty ("", 0, and ""
	// respectively). In this case, DeleteRecords will delete any records that
	// match the other fields, regardless of the value of the fields that were left
	// empty. Note that this behavior does *not* apply to the [libdns.Record.Name]
	// field, which must always be specified.
	//
	// Note that it is semantically invalid to remove the last “NS” record from a
	// zone, so attempting to do is undefined behavior.
	//
	// Implementations should return struct types defined by this package which
	// correspond with the specific RR-type (instead of the opaque [RR] struct).
	//
	// Implementations must honor context cancellation and be safe for concurrent
	// use.
	DeleteRecords(ctx context.Context, zone string, recs []Record) ([]Record, error)
}

RecordDeleter can delete records from a DNS zone.

type RecordGetter

type RecordGetter interface {
	// GetRecords returns all the records in the DNS zone.
	//
	// DNSSEC-related records are typically not included in the output, but this
	// behavior is implementation-defined. If an implementation includes DNSSEC
	// records in the output, this behavior should be documented.
	//
	// Implementations must honor context cancellation and be safe for concurrent
	// use.
	GetRecords(ctx context.Context, zone string) ([]Record, error)
}

RecordGetter can get records from a DNS zone.

type RecordSetter

type RecordSetter interface {
	// SetRecords updates the zone so that the records described in the input are
	// reflected in the output. It may create or update records or—depending on
	// the record type—delete records to maintain parity with the input. No other
	// records are affected. It returns the records which were set.
	//
	// For any (name, type) pair in the input, SetRecords ensures that the only
	// records in the output zone with that (name, type) pair are those that were
	// provided in the input.
	//
	// In RFC 9499 terms, SetRecords appends, modifies, or deletes records in the
	// zone so that for each RRset in the input, the records provided in the input
	// are the only members of their RRset in the output zone.
	//
	// SetRecords is distinct from [libdns.RecordAppender.AppendRecords] in that
	// AppendRecords *only* adds records to the zone, while SetRecords may also
	// delete records if necessary. Therefore, SetRecords behaves similarly to
	// the following code:
	//
	//	func SetRecords(ctx context.Context, zone string, recs []Record) ([]Record, error) {
	//		prevs, _ := p.GetRecords(ctx, zone)
	//		toDelete := []Record{}
	//		for _, prev := range prevs {
	//			for _, new := range recs {
	//				if prev.RR().Name == new.RR().Name && prev.RR().Type == new.RR().Type {
	//					toDelete = append(toDelete, prev)
	//				}
	//			}
	//		}
	//		DeleteRecords(ctx, zone, toDelete)
	//		return AppendRecords(ctx, zone, recs)
	//	}
	//
	// Implementations may decide whether or not to support DNSSEC-related records
	// in calls to SetRecords, but should document their decision. Note that the
	// decision to support DNSSEC records in SetRecords is independent of the
	// decision to support them in [libdns.RecordGetter.GetRecords], so callers
	// should not blindly call SetRecords with the output of
	// [libdns.RecordGetter.GetRecords].
	//
	// If possible, implementations should make SetRecords atomic, such that if
	// err == nil, then all of the requested changes were made, and if err != nil,
	// then the zone remains as if the method was never called. However, as very
	// few providers offer batch/atomic operations, the actual result of a call
	// where err != nil is undefined. Implementations may implement synthetic
	// atomicity that rolls back partial changes on failure ONLY if it can be
	// done reliably. For calls that error atomically, implementations should
	// return [AtomicErr] as the error so callers may know that their zone remains
	// in a consistent state. Implementations should document their atomicity
	// guarantees (or lack thereof).
	//
	// If SetRecords is used to add a CNAME record to a name with other existing
	// non-DNSSEC records, implementations may either fail with an error, add
	// the CNAME and leave the other records in place (in violation of the DNS
	// standards), or add the CNAME and remove the other preexisting records.
	// Therefore, users should proceed with caution when using SetRecords with
	// CNAME records.
	//
	// Implementations should return struct types defined by this package which
	// correspond with the specific RR-type (instead of the opaque [RR] struct).
	//
	// Implementations must honor context cancellation and be safe for concurrent
	// use.
	//
	// # Examples
	//
	// Example 1:
	//
	//	;; Original zone
	//	example.com. 3600 IN A   192.0.2.1
	//	example.com. 3600 IN A   192.0.2.2
	//	example.com. 3600 IN TXT "hello world"
	//
	//	;; Input
	//	example.com. 3600 IN A   192.0.2.3
	//
	//	;; Resultant zone
	//	example.com. 3600 IN A   192.0.2.3
	//	example.com. 3600 IN TXT "hello world"
	//
	// Example 2:
	//
	//	;; Original zone
	//	alpha.example.com. 3600 IN AAAA 2001:db8::1
	//	alpha.example.com. 3600 IN AAAA 2001:db8::2
	//	beta.example.com.  3600 IN AAAA 2001:db8::3
	//	beta.example.com.  3600 IN AAAA 2001:db8::4
	//
	//	;; Input
	//	alpha.example.com. 3600 IN AAAA 2001:db8::1
	//	alpha.example.com. 3600 IN AAAA 2001:db8::2
	//	alpha.example.com. 3600 IN AAAA 2001:db8::5
	//
	//	;; Resultant zone
	//	alpha.example.com. 3600 IN AAAA 2001:db8::1
	//	alpha.example.com. 3600 IN AAAA 2001:db8::2
	//	alpha.example.com. 3600 IN AAAA 2001:db8::5
	//	beta.example.com.  3600 IN AAAA 2001:db8::3
	//	beta.example.com.  3600 IN AAAA 2001:db8::4
	SetRecords(ctx context.Context, zone string, recs []Record) ([]Record, error)
}

RecordSetter can set new or update existing records in a DNS zone.

type SRV added in v0.2.2

type SRV struct {
	// “Service” is the name of the service being offered, without the leading
	// underscore. The correct value for this field is defined by the service
	// that you are serving (and is typically registered with IANA). Some
	// examples include "sip", "xmpp", "ldap", "minecraft", "stun", "turn", etc.
	Service string

	// “Transport” is the name of the transport protocol used by the service,
	// without the leading underscore. This is almost always "tcp" or "udp", but
	// "sctp" and "dccp" are technically valid as well.
	//
	// Note that RFC 2782 defines this field as “Proto[col]”, but we're using
	// the updated name “Transport” from RFC 6335 in order to avoid confusion
	// with the similarly-named field in the SVCB record type.
	Transport string

	Name     string
	TTL      time.Duration
	Priority uint16 // Lower values indicate that clients should prefer this server
	Weight   uint16 // Higher values indicate that clients should prefer this server when choosing between targets with the same priority
	Port     uint16 // The port on which the service is running.
	Target   string // The hostname of the server providing the service, which must not point to a CNAME.

	// Optional custom data associated with the provider serving this record.
	// See the package godoc for important details on this field.
	ProviderData any
}

SRV represents a parsed SRV-type record, which is used to manifest services or instances that provide services on a network.

The serialization of this record type takes the form:

_service._proto.name. ttl IN SRV priority weight port target.

Note that all fields are mandatory.

func (SRV) RR added in v1.0.0

func (s SRV) RR() RR

type ServiceBinding added in v1.0.0

type ServiceBinding struct {
	// “Scheme” is the scheme of the URL used to access the service, or some
	// other protocol identifier registered with IANA. This field should not
	// contain a leading underscore.
	//
	// If the scheme is set to "https", then a HTTPS-type record will be
	// generated; for all other schemes, a SVCB-type record will be generated.
	// As defined in RFC 9460, the schemes "http", "wss", and "ws" also map to
	// HTTPS records.
	//
	// Note that if a new SVCB-compatible RR type is defined and specified as
	// mapping to a scheme, then [libdns] may automatically generate that type
	// instead of SVCB at some point in the future. It is expected that any RFC
	// that proposes such a new type will ensure that this does not cause any
	// backwards compatibility issues.
	Scheme string

	// Warning: This field almost certainly does not do what you expect, and
	// should typically be unset (or set to 0).
	//
	// “URLSchemePort” is the port number that is explictly specified in a URL
	// when accessing a service. This field does not affect the port number that
	// is actually used to access the service, and unlike with SRV records, it
	// must be unset if you are using the default port for the scheme.
	//
	// # Examples
	//
	// In the typical case, you would have the following URL:
	//
	//  https://example.com/
	//
	// and then the client would lookup the following records:
	//
	//  example.com.  60  IN  HTTPS  1  example.net.  alpn=h2,h3
	//  example.net.  60  IN  A      192.0.2.1
	//
	// and then the client would connect to 192.0.2.1:443. But if you had the
	// same URL but the following records:
	//
	//  example.com.  60  IN  HTTPS  1  example.net.  alpn=h2,h3 port=1111
	//  example.net.  60  IN  A      192.0.2.2
	//
	// then the client would connect to 192.0.2.2:1111. But if you had the
	// following URL:
	//
	//  https://example.com:2222/
	//
	// then the client would lookup the following records:
	//
	//  _2222._https.example.com.  60  IN  HTTPS  1  example.net.  alpn=h2,h3
	//  example.net.               60  IN  A      192.0.2.3
	//
	// and the client would connect to 192.0.2.3:2222. And if you had the same
	// URL but the following records:
	//
	//  _2222._https.example.com.  60  IN  HTTPS  1  example.net.  alpn=h2,h3 port=3333
	//  example.net.               60  IN  A      192.0.2.4
	//
	// then the client would connect to 192.0.2.4:3333.
	//
	// So the key things to note here are that:
	//
	//  - If you want to change the port that the client connects to, you need
	//    to set the “port=” value in the “Params” field, not the
	//    “URLSchemePort”.
	//
	//  - The client will never lookup the HTTPS record prefixed with the
	//    underscored default port, so you should only set “URLSchemePort” if
	//    you are explicitly using a non-default port in the URL.
	//
	//  - It is completely valid to set the “port=” value in the “Params” field
	//    to the default port for the scheme, but also completely unnecessary.
	//
	//  - The “URLSchemePort” field and the “port=” value in the “Params” field
	//    are completely independent, with one exception: if you set the
	//    “URLSchemePort” field to a non-default port and leave the “port=”
	//    value in the “Params” field unset, then the client will default to the
	//    value of the “URLSchemePort” field, and not to the default port for
	//    the scheme.
	URLSchemePort uint16

	Name string
	TTL  time.Duration

	// “Priority” is the priority of the service, with lower values indicating
	// that clients should prefer this service over others.
	//
	// Note that Priority==0 is a special case, and indicates that the record
	// is an “Alias” record. Alias records behave like CNAME records, but are
	// allowed at the root of a zone. When in Alias mode, the Params field
	// should be unset.
	Priority uint16

	// “Target” is the target of the service, which is typically a hostname or
	// an alias (CNAME or other SVCB record). If this field is set to a single
	// dot ".", then the target is the same as the name of the record (without
	// the underscore-prefixed components, of course).
	Target string

	// “Params” is a map of key–value pairs that are used to specify various
	// parameters for the service. The keys are typically registered with IANA,
	// and which keys are valid is service-dependent.
	// https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
	//
	// Note that there is a key called “mandatory”, but this does not mean that
	// it is mandatory for you to set the listed keys. Instead, this means that
	// if a client does not understand all of the listed keys, then it must
	// ignore the entire record. This is similar to the “critical” flag in CAA
	// records.
	Params SvcParams

	// Optional custom data associated with the provider serving this record.
	// See the package godoc for important details on this field.
	ProviderData any
}

ServiceBinding represents a parsed ServiceBinding-type record, which is used to provide the target and various key–value parameters for a service. HTTPS records are defined as a “ServiceBinding-Compatible RR Type”, which means that their data structures are identical to ServiceBinding records, albeit with a different type name and semantics.

HTTPS-type records are used to provide clients with information for establishing HTTPS connections to servers. It may include data about ALPN, ECH, IP hints, and more.

Unlike the other RR types that are hostname-focused or service-focused, ServiceBinding (“Service Binding”) records are URL-focused. This distinction is generally irrelevant, but is important when disusing the port fields.

func (ServiceBinding) RR added in v1.0.0

func (s ServiceBinding) RR() RR

RR converts the parsed record data to a generic Record struct.

EXPERIMENTAL; subject to change or removal.

type SvcParams added in v1.0.0

type SvcParams map[string][]string

SvcParams represents SvcParamKey=SvcParamValue pairs as described in RFC 9460 section 2.1. See https://www.rfc-editor.org/rfc/rfc9460#presentation.

Note that this type is not primitively comparable, so using == for structs containnig a field of this type will panic.

func ParseSvcParams added in v1.0.0

func ParseSvcParams(input string) (SvcParams, error)

ParseSvcParams parses a SvcParams string described by RFC 9460 into a structured type.

func (SvcParams) String added in v1.0.0

func (params SvcParams) String() string

String serializes svcParams into zone presentation format described by RFC 9460.

type TXT added in v1.0.0

type TXT struct {
	Name string
	TTL  time.Duration

	// The “Text” field contains the arbitrary data associated with the TXT
	// record. The contents of this field should *not* be wrapped in quotes as
	// libdns implementations are expected to quote any fields as necessary. In
	// addition, as discussed in the description of [libdns.RR.Data], you should
	// not include any escaped characters in this field, as libdns will escape
	// them for you.
	//
	// In the zone file format and the DNS wire format, a single TXT record is
	// composed of one or more strings of no more than 255 bytes each ([RFC 1035
	// §3.3.14], [RFC 7208 §3.3]). We eschew those restrictions here, and
	// instead treat the entire TXT as a single, arbitrary-length string. libdns
	// implementations are therefore expected to handle this as required by
	// their respective DNS provider APIs. See the [DNSControl explainer] on
	// this for more information.
	//
	// [RFC 1035 §3.3.14]: https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.14
	// [RFC 7208 §3.3]: https://datatracker.ietf.org/doc/html/rfc7208#section-3.3
	// [DNSControl explainer]: https://docs.dnscontrol.org/developer-info/opinions#opinion-8-txt-records-are-one-long-string
	Text string

	// Optional custom data associated with the provider serving this record.
	// See the package godoc for important details on this field.
	ProviderData any
}

TXT represents a parsed TXT-type record, which is used to add arbitrary text data to a name in a DNS zone. It is often used for email integrity (DKIM/SPF), site verification, ACME challenges, and more.

func (TXT) RR added in v1.0.0

func (t TXT) RR() RR

type Zone added in v0.2.2

type Zone struct {
	Name string
}

Zone is a generalized representation of a DNS zone.

type ZoneLister added in v0.2.2

type ZoneLister interface {
	// ListZones returns the list of available DNS zones for use by other
	// [libdns] methods. Not every upstream provider API supports listing
	// available zones, and very few [libdns]-dependent packages use this
	// method, so this method is optional.
	//
	// Implementations must honor context cancellation and be safe for
	// concurrent use.
	ListZones(ctx context.Context) ([]Zone, error)
}

ZoneLister can list available DNS zones.

Jump to

Keyboard shortcuts

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