implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery

the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.

dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.

but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.

mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.

mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.

with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
This commit is contained in:
Mechiel Lukkien
2023-10-10 12:09:35 +02:00
parent c4324fdaa1
commit daa908e9f4
177 changed files with 12907 additions and 3131 deletions

1
vendor/github.com/mjl-/adns/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
/cover.*

27
vendor/github.com/mjl-/adns/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

33
vendor/github.com/mjl-/adns/Makefile generated vendored Normal file
View File

@ -0,0 +1,33 @@
build:
go build
go vet ./...
test:
go test -race -shuffle=on -coverprofile cover.out -covermode atomic
go tool cover -html=cover.out -o cover.html
check:
GOARCH=386 go vet
staticcheck ./...
# having "err" shadowed is common, best to not have others
check-shadow:
go vet -vettool=$$(which shadow) ./... 2>&1 | grep -v '"err"'
buildall:
GOOS=linux GOARCH=arm go build
GOOS=linux GOARCH=arm64 go build
GOOS=linux GOARCH=amd64 go build
GOOS=linux GOARCH=386 go build
GOOS=openbsd GOARCH=amd64 go build
GOOS=freebsd GOARCH=amd64 go build
GOOS=netbsd GOARCH=amd64 go build
GOOS=darwin GOARCH=amd64 go build
GOOS=dragonfly GOARCH=amd64 go build
GOOS=illumos GOARCH=amd64 go build
GOOS=solaris GOARCH=amd64 go build
GOOS=aix GOARCH=ppc64 go build
# no windows or plan9 for now
fmt:
gofmt -w -s *.go */*/*.go

5
vendor/github.com/mjl-/adns/README.md generated vendored Normal file
View File

@ -0,0 +1,5 @@
adns - copy of pure Go resolver from Go standard library, with modifications to facilitate use with DNSSEC.
Documentation: https://pkg.go.dev/github.com/mjl-/adns
License: Go's BSD license, see LICENSE.

377
vendor/github.com/mjl-/adns/addrselect.go generated vendored Normal file
View File

@ -0,0 +1,377 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Minimal RFC 6724 address selection.
package adns
import (
"net"
"net/netip"
"sort"
)
func sortByRFC6724(addrs []net.IPAddr) {
if len(addrs) < 2 {
return
}
sortByRFC6724withSrcs(addrs, srcAddrs(addrs))
}
func sortByRFC6724withSrcs(addrs []net.IPAddr, srcs []netip.Addr) {
if len(addrs) != len(srcs) {
panic("internal error")
}
addrAttr := make([]ipAttr, len(addrs))
srcAttr := make([]ipAttr, len(srcs))
for i, v := range addrs {
addrAttrIP, _ := netip.AddrFromSlice(v.IP)
addrAttr[i] = ipAttrOf(addrAttrIP)
srcAttr[i] = ipAttrOf(srcs[i])
}
sort.Stable(&byRFC6724{
addrs: addrs,
addrAttr: addrAttr,
srcs: srcs,
srcAttr: srcAttr,
})
}
// srcAddrs tries to UDP-connect to each address to see if it has a
// route. (This doesn't send any packets). The destination port
// number is irrelevant.
func srcAddrs(addrs []net.IPAddr) []netip.Addr {
srcs := make([]netip.Addr, len(addrs))
dst := net.UDPAddr{Port: 9}
for i := range addrs {
dst.IP = addrs[i].IP
dst.Zone = addrs[i].Zone
c, err := net.DialUDP("udp", nil, &dst)
if err == nil {
if src, ok := c.LocalAddr().(*net.UDPAddr); ok {
srcs[i], _ = netip.AddrFromSlice(src.IP)
}
c.Close()
}
}
return srcs
}
type ipAttr struct {
Scope scope
Precedence uint8
Label uint8
}
func ipAttrOf(ip netip.Addr) ipAttr {
if !ip.IsValid() {
return ipAttr{}
}
match := rfc6724policyTable.Classify(ip)
return ipAttr{
Scope: classifyScope(ip),
Precedence: match.Precedence,
Label: match.Label,
}
}
type byRFC6724 struct {
addrs []net.IPAddr // addrs to sort
addrAttr []ipAttr
srcs []netip.Addr // or not valid addr if unreachable
srcAttr []ipAttr
}
func (s *byRFC6724) Len() int { return len(s.addrs) }
func (s *byRFC6724) Swap(i, j int) {
s.addrs[i], s.addrs[j] = s.addrs[j], s.addrs[i]
s.srcs[i], s.srcs[j] = s.srcs[j], s.srcs[i]
s.addrAttr[i], s.addrAttr[j] = s.addrAttr[j], s.addrAttr[i]
s.srcAttr[i], s.srcAttr[j] = s.srcAttr[j], s.srcAttr[i]
}
// Less reports whether i is a better destination address for this
// host than j.
//
// The algorithm and variable names comes from RFC 6724 section 6.
func (s *byRFC6724) Less(i, j int) bool {
DA := s.addrs[i].IP
DB := s.addrs[j].IP
SourceDA := s.srcs[i]
SourceDB := s.srcs[j]
attrDA := &s.addrAttr[i]
attrDB := &s.addrAttr[j]
attrSourceDA := &s.srcAttr[i]
attrSourceDB := &s.srcAttr[j]
const preferDA = true
const preferDB = false
// Rule 1: Avoid unusable destinations.
// If DB is known to be unreachable or if Source(DB) is undefined, then
// prefer DA. Similarly, if DA is known to be unreachable or if
// Source(DA) is undefined, then prefer DB.
if !SourceDA.IsValid() && !SourceDB.IsValid() {
return false // "equal"
}
if !SourceDB.IsValid() {
return preferDA
}
if !SourceDA.IsValid() {
return preferDB
}
// Rule 2: Prefer matching scope.
// If Scope(DA) = Scope(Source(DA)) and Scope(DB) <> Scope(Source(DB)),
// then prefer DA. Similarly, if Scope(DA) <> Scope(Source(DA)) and
// Scope(DB) = Scope(Source(DB)), then prefer DB.
if attrDA.Scope == attrSourceDA.Scope && attrDB.Scope != attrSourceDB.Scope {
return preferDA
}
if attrDA.Scope != attrSourceDA.Scope && attrDB.Scope == attrSourceDB.Scope {
return preferDB
}
// Rule 3: Avoid deprecated addresses.
// If Source(DA) is deprecated and Source(DB) is not, then prefer DB.
// Similarly, if Source(DA) is not deprecated and Source(DB) is
// deprecated, then prefer DA.
// TODO(bradfitz): implement? low priority for now.
// Rule 4: Prefer home addresses.
// If Source(DA) is simultaneously a home address and care-of address
// and Source(DB) is not, then prefer DA. Similarly, if Source(DB) is
// simultaneously a home address and care-of address and Source(DA) is
// not, then prefer DB.
// TODO(bradfitz): implement? low priority for now.
// Rule 5: Prefer matching label.
// If Label(Source(DA)) = Label(DA) and Label(Source(DB)) <> Label(DB),
// then prefer DA. Similarly, if Label(Source(DA)) <> Label(DA) and
// Label(Source(DB)) = Label(DB), then prefer DB.
if attrSourceDA.Label == attrDA.Label &&
attrSourceDB.Label != attrDB.Label {
return preferDA
}
if attrSourceDA.Label != attrDA.Label &&
attrSourceDB.Label == attrDB.Label {
return preferDB
}
// Rule 6: Prefer higher precedence.
// If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if
// Precedence(DA) < Precedence(DB), then prefer DB.
if attrDA.Precedence > attrDB.Precedence {
return preferDA
}
if attrDA.Precedence < attrDB.Precedence {
return preferDB
}
// Rule 7: Prefer native transport.
// If DA is reached via an encapsulating transition mechanism (e.g.,
// IPv6 in IPv4) and DB is not, then prefer DB. Similarly, if DB is
// reached via encapsulation and DA is not, then prefer DA.
// TODO(bradfitz): implement? low priority for now.
// Rule 8: Prefer smaller scope.
// If Scope(DA) < Scope(DB), then prefer DA. Similarly, if Scope(DA) >
// Scope(DB), then prefer DB.
if attrDA.Scope < attrDB.Scope {
return preferDA
}
if attrDA.Scope > attrDB.Scope {
return preferDB
}
// Rule 9: Use the longest matching prefix.
// When DA and DB belong to the same address family (both are IPv6 or
// both are IPv4 [but see below]): If CommonPrefixLen(Source(DA), DA) >
// CommonPrefixLen(Source(DB), DB), then prefer DA. Similarly, if
// CommonPrefixLen(Source(DA), DA) < CommonPrefixLen(Source(DB), DB),
// then prefer DB.
//
// However, applying this rule to IPv4 addresses causes
// problems (see issues 13283 and 18518), so limit to IPv6.
if DA.To4() == nil && DB.To4() == nil {
commonA := commonPrefixLen(SourceDA, DA)
commonB := commonPrefixLen(SourceDB, DB)
if commonA > commonB {
return preferDA
}
if commonA < commonB {
return preferDB
}
}
// Rule 10: Otherwise, leave the order unchanged.
// If DA preceded DB in the original list, prefer DA.
// Otherwise, prefer DB.
return false // "equal"
}
type policyTableEntry struct {
Prefix netip.Prefix
Precedence uint8
Label uint8
}
type policyTable []policyTableEntry
// RFC 6724 section 2.1.
// Items are sorted by the size of their Prefix.Mask.Size,
var rfc6724policyTable = policyTable{
{
// "::1/128"
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}), 128),
Precedence: 50,
Label: 0,
},
{
// "::ffff:0:0/96"
// IPv4-compatible, etc.
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}), 96),
Precedence: 35,
Label: 4,
},
{
// "::/96"
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 96),
Precedence: 1,
Label: 3,
},
{
// "2001::/32"
// Teredo
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x01}), 32),
Precedence: 5,
Label: 5,
},
{
// "2002::/16"
// 6to4
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x02}), 16),
Precedence: 30,
Label: 2,
},
{
// "3ffe::/16"
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x3f, 0xfe}), 16),
Precedence: 1,
Label: 12,
},
{
// "fec0::/10"
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfe, 0xc0}), 10),
Precedence: 1,
Label: 11,
},
{
// "fc00::/7"
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfc}), 7),
Precedence: 3,
Label: 13,
},
{
// "::/0"
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0),
Precedence: 40,
Label: 1,
},
}
// Classify returns the policyTableEntry of the entry with the longest
// matching prefix that contains ip.
// The table t must be sorted from largest mask size to smallest.
func (t policyTable) Classify(ip netip.Addr) policyTableEntry {
// Prefix.Contains() will not match an IPv6 prefix for an IPv4 address.
if ip.Is4() {
ip = netip.AddrFrom16(ip.As16())
}
for _, ent := range t {
if ent.Prefix.Contains(ip) {
return ent
}
}
return policyTableEntry{}
}
// RFC 6724 section 3.1.
type scope uint8
const (
scopeInterfaceLocal scope = 0x1
scopeLinkLocal scope = 0x2
scopeAdminLocal scope = 0x4
scopeSiteLocal scope = 0x5
scopeOrgLocal scope = 0x8
scopeGlobal scope = 0xe
)
func classifyScope(ip netip.Addr) scope {
if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
return scopeLinkLocal
}
ipv6 := ip.Is6() && !ip.Is4In6()
ipv6AsBytes := ip.As16()
if ipv6 && ip.IsMulticast() {
return scope(ipv6AsBytes[1] & 0xf)
}
// Site-local addresses are defined in RFC 3513 section 2.5.6
// (and deprecated in RFC 3879).
if ipv6 && ipv6AsBytes[0] == 0xfe && ipv6AsBytes[1]&0xc0 == 0xc0 {
return scopeSiteLocal
}
return scopeGlobal
}
// commonPrefixLen reports the length of the longest prefix (looking
// at the most significant, or leftmost, bits) that the
// two addresses have in common, up to the length of a's prefix (i.e.,
// the portion of the address not including the interface ID).
//
// If a or b is an IPv4 address as an IPv6 address, the IPv4 addresses
// are compared (with max common prefix length of 32).
// If a and b are different IP versions, 0 is returned.
//
// See https://tools.ietf.org/html/rfc6724#section-2.2
func commonPrefixLen(a netip.Addr, b net.IP) (cpl int) {
if b4 := b.To4(); b4 != nil {
b = b4
}
aAsSlice := a.AsSlice()
if len(aAsSlice) != len(b) {
return 0
}
// If IPv6, only up to the prefix (first 64 bits)
if len(aAsSlice) > 8 {
aAsSlice = aAsSlice[:8]
b = b[:8]
}
for len(aAsSlice) > 0 {
if aAsSlice[0] == b[0] {
cpl += 8
aAsSlice = aAsSlice[1:]
b = b[1:]
continue
}
bits := 8
ab, bb := aAsSlice[0], b[0]
for {
ab >>= 1
bb >>= 1
bits--
if ab == bb {
cpl += bits
return
}
}
}
return
}

17
vendor/github.com/mjl-/adns/authentic.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
package adns
// Result has additional information about a DNS lookup.
type Result struct {
// Authentic indicates whether the response was DNSSEC-signed and verified.
// This package is a security-aware non-validating stub-resolver, sending requests
// with the "authentic data" bit set to its recursive resolvers, but only if the
// resolvers are trusted. Resolvers are trusted either if explicitly marked with
// "options trust-ad" in /etc/resolv.conf, or if all resolver IP addresses are
// loopback IP's. If the response from the resolver has the "authentic data" bit
// set, the DNS name and all indirections towards the name, were signed and the
// recursive resolver has verified them.
Authentic bool
// todo: possibly add followed cname's
// todo: possibly add lowest TTL encountered in lookup (gathered after following cname's)
}

508
vendor/github.com/mjl-/adns/conf.go generated vendored Normal file
View File

@ -0,0 +1,508 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !js
package adns
import (
"errors"
"io/fs"
"os"
"runtime"
"sync"
"syscall"
"github.com/mjl-/adns/internal/bytealg"
)
// The net package's name resolution is rather complicated.
// There are two main approaches, go and cgo.
// The cgo resolver uses C functions like getaddrinfo.
// The go resolver reads system files directly and
// sends DNS packets directly to servers.
//
// The netgo build tag prefers the go resolver.
// The netcgo build tag prefers the cgo resolver.
//
// The netgo build tag also prohibits the use of the cgo tool.
// However, on Darwin, Plan 9, and Windows the cgo resolver is still available.
// On those systems the cgo resolver does not require the cgo tool.
// (The term "cgo resolver" was locked in by GODEBUG settings
// at a time when the cgo resolver did require the cgo tool.)
//
// Adding netdns=go to GODEBUG will prefer the go resolver.
// Adding netdns=cgo to GODEBUG will prefer the cgo resolver.
//
// The Resolver struct has a PreferGo field that user code
// may set to prefer the go resolver. It is documented as being
// equivalent to adding netdns=go to GODEBUG.
//
// When deciding which resolver to use, we first check the PreferGo field.
// If that is not set, we check the GODEBUG setting.
// If that is not set, we check the netgo or netcgo build tag.
// If none of those are set, we normally prefer the go resolver by default.
// However, if the cgo resolver is available,
// there is a complex set of conditions for which we prefer the cgo resolver.
//
// Other files define the netGoBuildTag, netCgoBuildTag, and cgoAvailable
// constants.
// conf is used to determine name resolution configuration.
type conf struct {
netGo bool // prefer go approach, based on build tag and GODEBUG
netCgo bool // prefer cgo approach, based on build tag and GODEBUG
dnsDebugLevel int // from GODEBUG
preferCgo bool // if no explicit preference, use cgo
goos string // copy of runtime.GOOS, used for testing
mdnsTest mdnsTest // assume /etc/mdns.allow exists, for testing
}
// mdnsTest is for testing only.
type mdnsTest int
const (
mdnsFromSystem mdnsTest = iota
mdnsAssumeExists
mdnsAssumeDoesNotExist
)
var (
confOnce sync.Once // guards init of confVal via initConfVal
confVal = &conf{goos: runtime.GOOS}
)
// systemConf returns the machine's network configuration.
func systemConf() *conf {
confOnce.Do(initConfVal)
return confVal
}
var netGoBuildTag = true
var netCgoBuildTag = false
var cgoAvailable = false
// initConfVal initializes confVal based on the environment
// that will not change during program execution.
func initConfVal() {
dnsMode, debugLevel := goDebugNetDNS()
confVal.netGo = netGoBuildTag || dnsMode == "go"
confVal.netCgo = netCgoBuildTag || dnsMode == "cgo"
confVal.dnsDebugLevel = debugLevel
if confVal.dnsDebugLevel > 0 {
defer func() {
if confVal.dnsDebugLevel > 1 {
println("go package net: confVal.netCgo =", confVal.netCgo, " netGo =", confVal.netGo)
}
switch {
case confVal.netGo:
if netGoBuildTag {
println("go package net: built with netgo build tag; using Go's DNS resolver")
} else {
println("go package net: GODEBUG setting forcing use of Go's resolver")
}
case !cgoAvailable:
println("go package net: cgo resolver not supported; using Go's DNS resolver")
case confVal.netCgo || confVal.preferCgo:
println("go package net: using cgo DNS resolver")
default:
println("go package net: dynamic selection of DNS resolver")
}
}()
}
// The remainder of this function sets preferCgo based on
// conditions that will not change during program execution.
// By default, prefer the go resolver.
confVal.preferCgo = false
// If the cgo resolver is not available, we can't prefer it.
if !cgoAvailable {
return
}
// Some operating systems always prefer the cgo resolver.
if goosPrefersCgo() {
confVal.preferCgo = true
return
}
// The remaining checks are specific to Unix systems.
switch runtime.GOOS {
case "plan9", "windows", "js", "wasip1":
return
}
// If any environment-specified resolver options are specified,
// prefer the cgo resolver.
// Note that LOCALDOMAIN can change behavior merely by being
// specified with the empty string.
_, localDomainDefined := syscall.Getenv("LOCALDOMAIN")
if localDomainDefined || os.Getenv("RES_OPTIONS") != "" || os.Getenv("HOSTALIASES") != "" {
confVal.preferCgo = true
return
}
// OpenBSD apparently lets you override the location of resolv.conf
// with ASR_CONFIG. If we notice that, defer to libc.
if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" {
confVal.preferCgo = true
return
}
}
// goosPreferCgo reports whether the GOOS value passed in prefers
// the cgo resolver.
func goosPrefersCgo() bool {
switch runtime.GOOS {
// Historically on Windows and Plan 9 we prefer the
// cgo resolver (which doesn't use the cgo tool) rather than
// the go resolver. This is because originally these
// systems did not support the go resolver.
// Keep it this way for better compatibility.
// Perhaps we can revisit this some day.
case "windows", "plan9":
return true
// Darwin pops up annoying dialog boxes if programs try to
// do their own DNS requests, so prefer cgo.
case "darwin", "ios":
return true
// DNS requests don't work on Android, so prefer the cgo resolver.
// Issue #10714.
case "android":
return true
default:
return false
}
}
// mustUseGoResolver reports whether a DNS lookup of any sort is
// required to use the go resolver. The provided Resolver is optional.
// This will report true if the cgo resolver is not available.
func (c *conf) mustUseGoResolver(r *Resolver) bool {
return c.netGo || r.preferGo() || !cgoAvailable
}
// addrLookupOrder determines which strategy to use to resolve addresses.
// The provided Resolver is optional. nil means to not consider its options.
// It also returns dnsConfig when it was used to determine the lookup order.
func (c *conf) addrLookupOrder(r *Resolver, addr string) (ret hostLookupOrder, dnsConf *dnsConfig) {
if c.dnsDebugLevel > 1 {
defer func() {
print("go package net: addrLookupOrder(", addr, ") = ", ret.String(), "\n")
}()
}
return c.lookupOrder(r, "")
}
// hostLookupOrder determines which strategy to use to resolve hostname.
// The provided Resolver is optional. nil means to not consider its options.
// It also returns dnsConfig when it was used to determine the lookup order.
func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) {
if c.dnsDebugLevel > 1 {
defer func() {
print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
}()
}
return c.lookupOrder(r, hostname)
}
func (c *conf) lookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) {
// fallbackOrder is the order we return if we can't figure it out.
var fallbackOrder hostLookupOrder
var canUseCgo bool
if c.mustUseGoResolver(r) {
// Go resolver was explicitly requested
// or cgo resolver is not available.
// Figure out the order below.
switch c.goos {
case "windows":
// TODO(bradfitz): implement files-based
// lookup on Windows too? I guess /etc/hosts
// kinda exists on Windows. But for now, only
// do DNS.
fallbackOrder = hostLookupDNS
default:
fallbackOrder = hostLookupFilesDNS
}
canUseCgo = false
} else if c.netCgo {
// Cgo resolver was explicitly requested.
return hostLookupCgo, nil
} else if c.preferCgo {
// Given a choice, we prefer the cgo resolver.
return hostLookupCgo, nil
} else {
// Neither resolver was explicitly requested
// and we have no preference.
if bytealg.IndexByteString(hostname, '\\') != -1 || bytealg.IndexByteString(hostname, '%') != -1 {
// Don't deal with special form hostnames
// with backslashes or '%'.
return hostLookupCgo, nil
}
// If something is unrecognized, use cgo.
fallbackOrder = hostLookupCgo
canUseCgo = true
}
// On systems that don't use /etc/resolv.conf or /etc/nsswitch.conf, we are done.
switch c.goos {
case "windows", "plan9", "android", "ios":
return fallbackOrder, nil
}
// Try to figure out the order to use for searches.
// If we don't recognize something, use fallbackOrder.
// That will use cgo unless the Go resolver was explicitly requested.
// If we do figure out the order, return something other
// than fallbackOrder to use the Go resolver with that order.
dnsConf = getSystemDNSConfig()
if canUseCgo && dnsConf.err != nil && !errors.Is(dnsConf.err, fs.ErrNotExist) && !errors.Is(dnsConf.err, fs.ErrPermission) {
// We can't read the resolv.conf file, so use cgo if we can.
return hostLookupCgo, dnsConf
}
if canUseCgo && dnsConf.unknownOpt {
// We didn't recognize something in resolv.conf,
// so use cgo if we can.
return hostLookupCgo, dnsConf
}
// OpenBSD is unique and doesn't use nsswitch.conf.
// It also doesn't support mDNS.
if c.goos == "openbsd" {
// OpenBSD's resolv.conf manpage says that a
// non-existent resolv.conf means "lookup" defaults
// to only "files", without DNS lookups.
if errors.Is(dnsConf.err, fs.ErrNotExist) {
return hostLookupFiles, dnsConf
}
lookup := dnsConf.lookup
if len(lookup) == 0 {
// https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5
// "If the lookup keyword is not used in the
// system's resolv.conf file then the assumed
// order is 'bind file'"
return hostLookupDNSFiles, dnsConf
}
if len(lookup) < 1 || len(lookup) > 2 {
// We don't recognize this format.
return fallbackOrder, dnsConf
}
switch lookup[0] {
case "bind":
if len(lookup) == 2 {
if lookup[1] == "file" {
return hostLookupDNSFiles, dnsConf
}
// Unrecognized.
return fallbackOrder, dnsConf
}
return hostLookupDNS, dnsConf
case "file":
if len(lookup) == 2 {
if lookup[1] == "bind" {
return hostLookupFilesDNS, dnsConf
}
// Unrecognized.
return fallbackOrder, dnsConf
}
return hostLookupFiles, dnsConf
default:
// Unrecognized.
return fallbackOrder, dnsConf
}
// We always return before this point.
// The code below is for non-OpenBSD.
}
// Canonicalize the hostname by removing any trailing dot.
if stringsHasSuffix(hostname, ".") {
hostname = hostname[:len(hostname)-1]
}
if canUseCgo && stringsHasSuffixFold(hostname, ".local") {
// Per RFC 6762, the ".local" TLD is special. And
// because Go's native resolver doesn't do mDNS or
// similar local resolution mechanisms, assume that
// libc might (via Avahi, etc) and use cgo.
return hostLookupCgo, dnsConf
}
nss := getSystemNSS()
srcs := nss.sources["hosts"]
// If /etc/nsswitch.conf doesn't exist or doesn't specify any
// sources for "hosts", assume Go's DNS will work fine.
if errors.Is(nss.err, fs.ErrNotExist) || (nss.err == nil && len(srcs) == 0) {
if canUseCgo && c.goos == "solaris" {
// illumos defaults to
// "nis [NOTFOUND=return] files",
// which the go resolver doesn't support.
return hostLookupCgo, dnsConf
}
return hostLookupFilesDNS, dnsConf
}
if nss.err != nil {
// We failed to parse or open nsswitch.conf, so
// we have nothing to base an order on.
return fallbackOrder, dnsConf
}
var hasDNSSource bool
var hasDNSSourceChecked bool
var filesSource, dnsSource bool
var first string
for i, src := range srcs {
if src.source == "files" || src.source == "dns" {
if canUseCgo && !src.standardCriteria() {
// non-standard; let libc deal with it.
return hostLookupCgo, dnsConf
}
if src.source == "files" {
filesSource = true
} else {
hasDNSSource = true
hasDNSSourceChecked = true
dnsSource = true
}
if first == "" {
first = src.source
}
continue
}
if canUseCgo {
switch {
case hostname != "" && src.source == "myhostname":
// Let the cgo resolver handle myhostname
// if we are looking up the local hostname.
if isLocalhost(hostname) || isGateway(hostname) || isOutbound(hostname) {
return hostLookupCgo, dnsConf
}
hn, err := getHostname()
if err != nil || stringsEqualFold(hostname, hn) {
return hostLookupCgo, dnsConf
}
continue
case hostname != "" && stringsHasPrefix(src.source, "mdns"):
// e.g. "mdns4", "mdns4_minimal"
// We already returned true before if it was *.local.
// libc wouldn't have found a hit on this anyway.
// We don't parse mdns.allow files. They're rare. If one
// exists, it might list other TLDs (besides .local) or even
// '*', so just let libc deal with it.
var haveMDNSAllow bool
switch c.mdnsTest {
case mdnsFromSystem:
_, err := os.Stat("/etc/mdns.allow")
if err != nil && !errors.Is(err, fs.ErrNotExist) {
// Let libc figure out what is going on.
return hostLookupCgo, dnsConf
}
haveMDNSAllow = err == nil
case mdnsAssumeExists:
haveMDNSAllow = true
case mdnsAssumeDoesNotExist:
haveMDNSAllow = false
}
if haveMDNSAllow {
return hostLookupCgo, dnsConf
}
continue
default:
// Some source we don't know how to deal with.
return hostLookupCgo, dnsConf
}
}
if !hasDNSSourceChecked {
hasDNSSourceChecked = true
for _, v := range srcs[i+1:] {
if v.source == "dns" {
hasDNSSource = true
break
}
}
}
// If we saw a source we don't recognize, which can only
// happen if we can't use the cgo resolver, treat it as DNS,
// but only when there is no dns in all other sources.
if !hasDNSSource {
dnsSource = true
if first == "" {
first = "dns"
}
}
}
// Cases where Go can handle it without cgo and C thread overhead,
// or where the Go resolver has been forced.
switch {
case filesSource && dnsSource:
if first == "files" {
return hostLookupFilesDNS, dnsConf
} else {
return hostLookupDNSFiles, dnsConf
}
case filesSource:
return hostLookupFiles, dnsConf
case dnsSource:
return hostLookupDNS, dnsConf
}
// Something weird. Fallback to the default.
return fallbackOrder, dnsConf
}
// goDebugNetDNS parses the value of the GODEBUG "netdns" value.
// The netdns value can be of the form:
//
// 1 // debug level 1
// 2 // debug level 2
// cgo // use cgo for DNS lookups
// go // use go for DNS lookups
// cgo+1 // use cgo for DNS lookups + debug level 1
// 1+cgo // same
// cgo+2 // same, but debug level 2
//
// etc.
func goDebugNetDNS() (dnsMode string, debugLevel int) {
return "go", 0
}
// isLocalhost reports whether h should be considered a "localhost"
// name for the myhostname NSS module.
func isLocalhost(h string) bool {
return stringsEqualFold(h, "localhost") || stringsEqualFold(h, "localhost.localdomain") || stringsHasSuffixFold(h, ".localhost") || stringsHasSuffixFold(h, ".localhost.localdomain")
}
// isGateway reports whether h should be considered a "gateway"
// name for the myhostname NSS module.
func isGateway(h string) bool {
return stringsEqualFold(h, "_gateway")
}
// isOutbound reports whether h should be considered a "outbound"
// name for the myhostname NSS module.
func isOutbound(h string) bool {
return stringsEqualFold(h, "_outbound")
}

42
vendor/github.com/mjl-/adns/dial.go generated vendored Normal file
View File

@ -0,0 +1,42 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package adns
import (
"context"
"net"
)
func parseNetwork(ctx context.Context, network string, needsProto bool) (afnet string, proto int, err error) {
i := last(network, ':')
if i < 0 { // no colon
switch network {
case "tcp", "tcp4", "tcp6":
case "udp", "udp4", "udp6":
case "ip", "ip4", "ip6":
if needsProto {
return "", 0, net.UnknownNetworkError(network)
}
case "unix", "unixgram", "unixpacket":
default:
return "", 0, net.UnknownNetworkError(network)
}
return network, 0, nil
}
afnet = network[:i]
switch afnet {
case "ip", "ip4", "ip6":
protostr := network[i+1:]
proto, i, ok := dtoi(protostr)
if !ok || i != len(protostr) {
proto, err = lookupProtocol(ctx, protostr)
if err != nil {
return "", 0, err
}
}
return afnet, proto, nil
}
return "", 0, net.UnknownNetworkError(network)
}

209
vendor/github.com/mjl-/adns/dnsclient.go generated vendored Normal file
View File

@ -0,0 +1,209 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package adns
import (
mathrand "math/rand"
"net"
"sort"
"golang.org/x/net/dns/dnsmessage"
"github.com/mjl-/adns/internal/bytealg"
"github.com/mjl-/adns/internal/itoa"
)
func randInt() int {
return mathrand.Int()
}
func randIntn(n int) int {
return randInt() % n
}
// reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
// address addr suitable for rDNS (PTR) record lookup or an error if it fails
// to parse the IP address.
func reverseaddr(addr string) (arpa string, err error) {
ip := net.ParseIP(addr)
if ip == nil {
return "", &DNSError{Err: "unrecognized address", Name: addr}
}
if ip.To4() != nil {
return itoa.Uitoa(uint(ip[15])) + "." + itoa.Uitoa(uint(ip[14])) + "." + itoa.Uitoa(uint(ip[13])) + "." + itoa.Uitoa(uint(ip[12])) + ".in-addr.arpa.", nil
}
// Must be IPv6
buf := make([]byte, 0, len(ip)*4+len("ip6.arpa."))
// Add it, in reverse, to the buffer
for i := len(ip) - 1; i >= 0; i-- {
v := ip[i]
buf = append(buf, hexDigit[v&0xF],
'.',
hexDigit[v>>4],
'.')
}
// Append "ip6.arpa." and return (buf already has the final .)
buf = append(buf, "ip6.arpa."...)
return string(buf), nil
}
func equalASCIIName(x, y dnsmessage.Name) bool {
if x.Length != y.Length {
return false
}
for i := 0; i < int(x.Length); i++ {
a := x.Data[i]
b := y.Data[i]
if 'A' <= a && a <= 'Z' {
a += 0x20
}
if 'A' <= b && b <= 'Z' {
b += 0x20
}
if a != b {
return false
}
}
return true
}
// isDomainName checks if a string is a presentation-format domain name
// (currently restricted to hostname-compatible "preferred name" LDH labels and
// SRV-like "underscore labels"; see golang.org/issue/12421).
func isDomainName(s string) bool {
// The root domain name is valid. See golang.org/issue/45715.
if s == "." {
return true
}
// See RFC 1035, RFC 3696.
// Presentation format has dots before every label except the first, and the
// terminal empty label is optional here because we assume fully-qualified
// (absolute) input. We must therefore reserve space for the first and last
// labels' length octets in wire format, where they are necessary and the
// maximum total length is 255.
// So our _effective_ maximum is 253, but 254 is not rejected if the last
// character is a dot.
l := len(s)
if l == 0 || l > 254 || l == 254 && s[l-1] != '.' {
return false
}
last := byte('.')
nonNumeric := false // true once we've seen a letter or hyphen
partlen := 0
for i := 0; i < len(s); i++ {
c := s[i]
switch {
default:
return false
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_':
nonNumeric = true
partlen++
case '0' <= c && c <= '9':
// fine
partlen++
case c == '-':
// Byte before dash cannot be dot.
if last == '.' {
return false
}
partlen++
nonNumeric = true
case c == '.':
// Byte before dot cannot be dot, dash.
if last == '.' || last == '-' {
return false
}
if partlen > 63 || partlen == 0 {
return false
}
partlen = 0
}
last = c
}
if last == '-' || partlen > 63 {
return false
}
return nonNumeric
}
// absDomainName returns an absolute domain name which ends with a
// trailing dot to match pure Go reverse resolver and all other lookup
// routines.
// See golang.org/issue/12189.
// But we don't want to add dots for local names from /etc/hosts.
// It's hard to tell so we settle on the heuristic that names without dots
// (like "localhost" or "myhost") do not get trailing dots, but any other
// names do.
func absDomainName(s string) string {
if bytealg.IndexByteString(s, '.') != -1 && s[len(s)-1] != '.' {
s += "."
}
return s
}
// byPriorityWeight sorts SRV records by ascending priority and weight.
type byPriorityWeight []*net.SRV
func (s byPriorityWeight) Len() int { return len(s) }
func (s byPriorityWeight) Less(i, j int) bool {
return s[i].Priority < s[j].Priority || (s[i].Priority == s[j].Priority && s[i].Weight < s[j].Weight)
}
func (s byPriorityWeight) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// shuffleByWeight shuffles SRV records by weight using the algorithm
// described in RFC 2782.
func (addrs byPriorityWeight) shuffleByWeight() {
sum := 0
for _, addr := range addrs {
sum += int(addr.Weight)
}
for sum > 0 && len(addrs) > 1 {
s := 0
n := randIntn(sum)
for i := range addrs {
s += int(addrs[i].Weight)
if s > n {
if i > 0 {
addrs[0], addrs[i] = addrs[i], addrs[0]
}
break
}
}
sum -= int(addrs[0].Weight)
addrs = addrs[1:]
}
}
// sort reorders SRV records as specified in RFC 2782.
func (addrs byPriorityWeight) sort() {
sort.Sort(addrs)
i := 0
for j := 1; j < len(addrs); j++ {
if addrs[i].Priority != addrs[j].Priority {
addrs[i:j].shuffleByWeight()
i = j
}
}
addrs[i:].shuffleByWeight()
}
// byPref implements sort.Interface to sort MX records by preference
type byPref []*net.MX
func (s byPref) Len() int { return len(s) }
func (s byPref) Less(i, j int) bool { return s[i].Pref < s[j].Pref }
func (s byPref) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// sort reorders MX records as specified in RFC 5321.
func (s byPref) sort() {
for i := range s {
j := randIntn(i + 1)
s[i], s[j] = s[j], s[i]
}
sort.Sort(s)
}

947
vendor/github.com/mjl-/adns/dnsclient_unix.go generated vendored Normal file
View File

@ -0,0 +1,947 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !js
// DNS client: see RFC 1035.
// Has to be linked into package net for Dial.
// TODO(rsc):
// Could potentially handle many outstanding lookups faster.
// Random UDP source port (net.Dial should do that for us).
// Random request IDs.
package adns
import (
"bytes"
"context"
cryptorand "crypto/rand"
"errors"
"io"
"net"
"os"
"runtime"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/dns/dnsmessage"
"github.com/mjl-/adns/internal/itoa"
)
const (
// to be used as a useTCP parameter to exchange
useTCPOnly = true
useUDPOrTCP = false
// Maximum DNS packet size.
// Value taken from https://dnsflagday.net/2020/.
maxDNSPacketSize = 1232
)
var (
errLameReferral = errors.New("lame referral")
errCannotUnmarshalDNSMessage = errors.New("cannot unmarshal DNS message")
errCannotMarshalDNSMessage = errors.New("cannot marshal DNS message")
errServerMisbehaving = errors.New("server misbehaving")
errInvalidDNSResponse = errors.New("invalid DNS response")
errNoAnswerFromDNSServer = errors.New("no answer from DNS server")
// errServerTemporarilyMisbehaving is like errServerMisbehaving, except
// that when it gets translated to a DNSError, the IsTemporary field
// gets set to true.
errServerTemporarilyMisbehaving = errors.New("server misbehaving")
)
func newRequest(q dnsmessage.Question, ad bool) (id uint16, udpReq, tcpReq []byte, err error) {
var idbuf [2]byte
_, err = cryptorand.Read(idbuf[:])
if err != nil {
return 0, nil, nil, err
}
id = uint16(idbuf[0])<<8 | uint16(idbuf[1])
b := dnsmessage.NewBuilder(make([]byte, 2, 514), dnsmessage.Header{ID: id, RecursionDesired: true, AuthenticData: ad})
if err := b.StartQuestions(); err != nil {
return 0, nil, nil, err
}
if err := b.Question(q); err != nil {
return 0, nil, nil, err
}
// Accept packets up to maxDNSPacketSize. RFC 6891.
if err := b.StartAdditionals(); err != nil {
return 0, nil, nil, err
}
var rh dnsmessage.ResourceHeader
if err := rh.SetEDNS0(maxDNSPacketSize, dnsmessage.RCodeSuccess, false); err != nil {
return 0, nil, nil, err
}
if err := b.OPTResource(rh, dnsmessage.OPTResource{}); err != nil {
return 0, nil, nil, err
}
tcpReq, err = b.Finish()
if err != nil {
return 0, nil, nil, err
}
udpReq = tcpReq[2:]
l := len(tcpReq) - 2
tcpReq[0] = byte(l >> 8)
tcpReq[1] = byte(l)
return id, udpReq, tcpReq, nil
}
func checkResponse(reqID uint16, reqQues dnsmessage.Question, respHdr dnsmessage.Header, respQues dnsmessage.Question) bool {
if !respHdr.Response {
return false
}
if reqID != respHdr.ID {
return false
}
if reqQues.Type != respQues.Type || reqQues.Class != respQues.Class || !equalASCIIName(reqQues.Name, respQues.Name) {
return false
}
return true
}
func dnsPacketRoundTrip(c net.Conn, id uint16, query dnsmessage.Question, b []byte) (dnsmessage.Parser, dnsmessage.Header, error) {
if _, err := c.Write(b); err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
b = make([]byte, maxDNSPacketSize)
for {
n, err := c.Read(b)
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
var p dnsmessage.Parser
// Ignore invalid responses as they may be malicious
// forgery attempts. Instead continue waiting until
// timeout. See golang.org/issue/13281.
h, err := p.Start(b[:n])
if err != nil {
continue
}
q, err := p.Question()
if err != nil || !checkResponse(id, query, h, q) {
continue
}
return p, h, nil
}
}
func dnsStreamRoundTrip(c net.Conn, id uint16, query dnsmessage.Question, b []byte) (dnsmessage.Parser, dnsmessage.Header, error) {
if _, err := c.Write(b); err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
b = make([]byte, 1280) // 1280 is a reasonable initial size for IP over Ethernet, see RFC 4035
if _, err := io.ReadFull(c, b[:2]); err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
l := int(b[0])<<8 | int(b[1])
if l > len(b) {
b = make([]byte, l)
}
n, err := io.ReadFull(c, b[:l])
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
var p dnsmessage.Parser
h, err := p.Start(b[:n])
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotUnmarshalDNSMessage
}
q, err := p.Question()
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotUnmarshalDNSMessage
}
if !checkResponse(id, query, h, q) {
return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse
}
return p, h, nil
}
// exchange sends a query on the connection and hopes for a response.
func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Question, timeout time.Duration, useTCP, ad bool) (dnsmessage.Parser, dnsmessage.Header, error) {
q.Class = dnsmessage.ClassINET
id, udpReq, tcpReq, err := newRequest(q, ad)
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotMarshalDNSMessage
}
var networks []string
if useTCP {
networks = []string{"tcp"}
} else {
networks = []string{"udp", "tcp"}
}
for _, network := range networks {
nctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
defer cancel()
c, err := r.dial(nctx, network, server)
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
if d, ok := nctx.Deadline(); ok && !d.IsZero() {
c.SetDeadline(d)
}
var p dnsmessage.Parser
var h dnsmessage.Header
if _, ok := c.(net.PacketConn); ok {
p, h, err = dnsPacketRoundTrip(c, id, q, udpReq)
} else {
p, h, err = dnsStreamRoundTrip(c, id, q, tcpReq)
}
c.Close()
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, mapErr(err)
}
if err := p.SkipQuestion(); err != dnsmessage.ErrSectionDone {
return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse
}
if h.Truncated { // see RFC 5966
continue
}
return p, h, nil
}
return dnsmessage.Parser{}, dnsmessage.Header{}, errNoAnswerFromDNSServer
}
// checkHeader performs basic sanity checks on the header.
func checkHeader(p *dnsmessage.Parser, h dnsmessage.Header) error {
if h.RCode == dnsmessage.RCodeNameError {
return errNoSuchHost
}
_, err := p.AnswerHeader()
if err != nil && err != dnsmessage.ErrSectionDone {
return errCannotUnmarshalDNSMessage
}
// libresolv continues to the next server when it receives
// an invalid referral response. See golang.org/issue/15434.
if h.RCode == dnsmessage.RCodeSuccess && !h.Authoritative && !h.RecursionAvailable && err == dnsmessage.ErrSectionDone {
return errLameReferral
}
if h.RCode != dnsmessage.RCodeSuccess && h.RCode != dnsmessage.RCodeNameError {
// None of the error codes make sense
// for the query we sent. If we didn't get
// a name error and we didn't get success,
// the server is behaving incorrectly or
// having temporary trouble.
if h.RCode == dnsmessage.RCodeServerFailure {
// Look for Extended DNS Error (EDE), RFC 8914.
if p.SkipAllAnswers() != nil || p.SkipAllAuthorities() != nil {
return errServerTemporarilyMisbehaving
}
var haveOPT bool
for {
rh, err := p.AdditionalHeader()
if err == dnsmessage.ErrSectionDone {
break
} else if err != nil {
return errServerTemporarilyMisbehaving
}
if rh.Type != dnsmessage.TypeOPT {
p.SkipAdditional()
continue
}
// Only one OPT record is allowed. With multiple we MUST return an error. See RFC
// 6891, section 6.1.1, page 6, last paragraph.
if haveOPT {
return errInvalidDNSResponse
}
haveOPT = true
opt, err := p.OPTResource()
if err != nil {
return errInvalidDNSResponse
}
for _, o := range opt.Options {
if o.Code == 15 {
if len(o.Data) < 2 {
return errInvalidDNSResponse
}
infoCode := ErrorCode(uint16(o.Data[0])<<8 | uint16(o.Data[1]<<0))
extraText := string(bytes.TrimRight(o.Data[2:], "\u0000"))
return ExtendedError{infoCode, extraText}
}
}
}
return errServerTemporarilyMisbehaving
}
return errServerMisbehaving
}
return nil
}
func skipToAnswer(p *dnsmessage.Parser, qtype dnsmessage.Type) error {
for {
h, err := p.AnswerHeader()
if err == dnsmessage.ErrSectionDone {
return errNoSuchHost
}
if err != nil {
return errCannotUnmarshalDNSMessage
}
if h.Type == qtype {
return nil
}
if err := p.SkipAnswer(); err != nil {
return errCannotUnmarshalDNSMessage
}
}
}
// Do a lookup for a single name, which must be rooted
// (otherwise answer will not find the answers).
func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype dnsmessage.Type) (dnsmessage.Parser, string, Result, error) {
var lastErr error
var lastResult Result
serverOffset := cfg.serverOffset()
sLen := uint32(len(cfg.servers))
n, err := dnsmessage.NewName(name)
if err != nil {
return dnsmessage.Parser{}, "", Result{}, errCannotMarshalDNSMessage
}
q := dnsmessage.Question{
Name: n,
Type: qtype,
Class: dnsmessage.ClassINET,
}
for i := 0; i < cfg.attempts; i++ {
for j := uint32(0); j < sLen; j++ {
server := cfg.servers[(serverOffset+j)%sLen]
p, h, err := r.exchange(ctx, server, q, cfg.timeout, cfg.useTCP, cfg.trustAD)
if err != nil {
dnsErr := &DNSError{
Err: err.Error(),
Name: name,
Server: server,
}
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
dnsErr.IsTimeout = true
}
// Set IsTemporary for socket-level errors. Note that this flag
// may also be used to indicate a SERVFAIL response.
if _, ok := err.(*net.OpError); ok {
dnsErr.IsTemporary = true
}
lastErr = dnsErr
lastResult = Result{}
continue
}
useAD := h.RCode == dnsmessage.RCodeSuccess || h.RCode == dnsmessage.RCodeNameError
result := Result{Authentic: cfg.trustAD && h.AuthenticData && useAD}
if err := checkHeader(&p, h); err != nil {
dnsErr := &DNSError{
Underlying: err,
Err: err.Error(),
Name: name,
Server: server,
}
if err == errServerTemporarilyMisbehaving {
dnsErr.IsTemporary = true
} else if edeErr, isEDE := err.(ExtendedError); isEDE && edeErr.IsTemporary() {
dnsErr.IsTemporary = true
} else if isEDE {
// Something wrong with the zone, no point asking another server or retrying.
return p, server, result, dnsErr
}
if err == errNoSuchHost {
// The name does not exist, so trying
// another server won't help.
dnsErr.IsNotFound = true
return p, server, result, dnsErr
}
lastErr = dnsErr
lastResult = result
continue
}
err = skipToAnswer(&p, qtype)
if err == nil {
return p, server, result, nil
}
lastErr = &DNSError{
Err: err.Error(),
Name: name,
Server: server,
}
lastResult = result
if err == errNoSuchHost {
// The name does not exist, so trying another
// server won't help.
lastErr.(*DNSError).IsNotFound = true
return p, server, lastResult, lastErr
}
}
}
return dnsmessage.Parser{}, "", lastResult, lastErr
}
// A resolverConfig represents a DNS stub resolver configuration.
type resolverConfig struct {
initOnce sync.Once // guards init of resolverConfig
// ch is used as a semaphore that only allows one lookup at a
// time to recheck resolv.conf.
ch chan struct{} // guards lastChecked and modTime
lastChecked time.Time // last time resolv.conf was checked
dnsConfig atomic.Pointer[dnsConfig] // parsed resolv.conf structure used in lookups
}
var resolvConf resolverConfig
func getSystemDNSConfig() *dnsConfig {
resolvConf.tryUpdate("/etc/resolv.conf")
return resolvConf.dnsConfig.Load()
}
// init initializes conf and is only called via conf.initOnce.
func (conf *resolverConfig) init() {
// Set dnsConfig and lastChecked so we don't parse
// resolv.conf twice the first time.
conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf"))
conf.lastChecked = time.Now()
// Prepare ch so that only one update of resolverConfig may
// run at once.
conf.ch = make(chan struct{}, 1)
}
// tryUpdate tries to update conf with the named resolv.conf file.
// The name variable only exists for testing. It is otherwise always
// "/etc/resolv.conf".
func (conf *resolverConfig) tryUpdate(name string) {
conf.initOnce.Do(conf.init)
if conf.dnsConfig.Load().noReload {
return
}
// Ensure only one update at a time checks resolv.conf.
if !conf.tryAcquireSema() {
return
}
defer conf.releaseSema()
now := time.Now()
if conf.lastChecked.After(now.Add(-5 * time.Second)) {
return
}
conf.lastChecked = now
switch runtime.GOOS {
case "windows":
// There's no file on disk, so don't bother checking
// and failing.
//
// The Windows implementation of dnsReadConfig (called
// below) ignores the name.
default:
var mtime time.Time
if fi, err := os.Stat(name); err == nil {
mtime = fi.ModTime()
}
if mtime.Equal(conf.dnsConfig.Load().mtime) {
return
}
}
dnsConf := dnsReadConfig(name)
conf.dnsConfig.Store(dnsConf)
}
func (conf *resolverConfig) tryAcquireSema() bool {
select {
case conf.ch <- struct{}{}:
return true
default:
return false
}
}
func (conf *resolverConfig) releaseSema() {
<-conf.ch
}
func (r *Resolver) lookup(ctx context.Context, name string, qtype dnsmessage.Type, conf *dnsConfig) (dnsmessage.Parser, string, Result, error) {
if !isDomainName(name) {
// We used to use "invalid domain name" as the error,
// but that is a detail of the specific lookup mechanism.
// Other lookups might allow broader name syntax
// (for example Multicast DNS allows UTF-8; see RFC 6762).
// For consistency with libc resolvers, report no such host.
return dnsmessage.Parser{}, "", Result{}, &DNSError{Err: errNoSuchHost.Error(), Name: name, IsNotFound: true}
}
if conf == nil {
conf = getSystemDNSConfig()
}
var (
p dnsmessage.Parser
server string
result Result
err error
)
for _, fqdn := range conf.nameList(name) {
p, server, result, err = r.tryOneName(ctx, conf, fqdn, qtype)
if err == nil {
break
}
var edeErr ExtendedError
if nerr, ok := err.(net.Error); ok && nerr.Temporary() && r.strictErrors() || errors.As(err, &edeErr) && !edeErr.IsTemporary() {
// If we hit a temporary error with StrictErrors enabled,
// stop immediately instead of trying more names.
break
}
}
if err == nil {
return p, server, result, nil
}
if err, ok := err.(*DNSError); ok {
// Show original name passed to lookup, not suffixed one.
// In general we might have tried many suffixes; showing
// just one is misleading. See also golang.org/issue/6324.
err.Name = name
}
return dnsmessage.Parser{}, "", result, err
}
// avoidDNS reports whether this is a hostname for which we should not
// use DNS. Currently this includes only .onion, per RFC 7686. See
// golang.org/issue/13705. Does not cover .local names (RFC 6762),
// see golang.org/issue/16739.
func avoidDNS(name string) bool {
if name == "" {
return true
}
if name[len(name)-1] == '.' {
name = name[:len(name)-1]
}
return stringsHasSuffixFold(name, ".onion")
}
// nameList returns a list of names for sequential DNS queries.
func (conf *dnsConfig) nameList(name string) []string {
if avoidDNS(name) {
return nil
}
// Check name length (see isDomainName).
l := len(name)
rooted := l > 0 && name[l-1] == '.'
if l > 254 || l == 254 && !rooted {
return nil
}
// If name is rooted (trailing dot), try only that name.
if rooted {
return []string{name}
}
hasNdots := count(name, '.') >= conf.ndots
name += "."
l++
// Build list of search choices.
names := make([]string, 0, 1+len(conf.search))
// If name has enough dots, try unsuffixed first.
if hasNdots {
names = append(names, name)
}
// Try suffixes that are not too long (see isDomainName).
for _, suffix := range conf.search {
if l+len(suffix) <= 254 {
names = append(names, name+suffix)
}
}
// Try unsuffixed, if not tried first above.
if !hasNdots {
names = append(names, name)
}
return names
}
// hostLookupOrder specifies the order of LookupHost lookup strategies.
// It is basically a simplified representation of nsswitch.conf.
// "files" means /etc/hosts.
type hostLookupOrder int
const (
// hostLookupCgo means defer to cgo.
hostLookupCgo hostLookupOrder = iota
hostLookupFilesDNS // files first
hostLookupDNSFiles // dns first
hostLookupFiles // only files
hostLookupDNS // only DNS
)
var lookupOrderName = map[hostLookupOrder]string{
hostLookupCgo: "cgo",
hostLookupFilesDNS: "files,dns",
hostLookupDNSFiles: "dns,files",
hostLookupFiles: "files",
hostLookupDNS: "dns",
}
func (o hostLookupOrder) String() string {
if s, ok := lookupOrderName[o]; ok {
return s
}
return "hostLookupOrder=" + itoa.Itoa(int(o)) + "??"
}
func (r *Resolver) goLookupHostOrder(ctx context.Context, name string, order hostLookupOrder, conf *dnsConfig) (addrs []string, result Result, err error) {
if order == hostLookupFilesDNS || order == hostLookupFiles {
// Use entries from /etc/hosts if they match.
addrs, _ = lookupStaticHost(name)
if len(addrs) > 0 {
return
}
if order == hostLookupFiles {
return nil, result, &DNSError{Err: errNoSuchHost.Error(), Name: name, IsNotFound: true}
}
}
ips, _, result, err := r.goLookupIPCNAMEOrder(ctx, "ip", name, order, conf)
if err != nil {
return
}
addrs = make([]string, 0, len(ips))
for _, ip := range ips {
addrs = append(addrs, ip.String())
}
return
}
// lookup entries from /etc/hosts
func goLookupIPFiles(name string) (addrs []net.IPAddr, canonical string) {
addr, canonical := lookupStaticHost(name)
for _, haddr := range addr {
xhaddr, zone := splitHostZone(haddr)
if ip := net.ParseIP(xhaddr); ip != nil {
addr := net.IPAddr{IP: ip, Zone: zone}
addrs = append(addrs, addr)
}
}
sortByRFC6724(addrs)
return addrs, canonical
}
// goLookupIP is the native Go implementation of LookupIP.
// The libc versions are in cgo_*.go.
func (r *Resolver) goLookupIP(ctx context.Context, network, host string) (addrs []net.IPAddr, result Result, err error) {
order, conf := systemConf().hostLookupOrder(r, host)
addrs, _, result, err = r.goLookupIPCNAMEOrder(ctx, network, host, order, conf)
return
}
func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, network, name string, order hostLookupOrder, conf *dnsConfig) (addrs []net.IPAddr, cname dnsmessage.Name, result Result, err error) {
if order == hostLookupFilesDNS || order == hostLookupFiles {
var canonical string
addrs, canonical = goLookupIPFiles(name)
if len(addrs) > 0 {
var err error
cname, err = dnsmessage.NewName(canonical)
if err != nil {
return nil, dnsmessage.Name{}, result, err
}
return addrs, cname, result, nil
}
if order == hostLookupFiles {
return nil, dnsmessage.Name{}, result, &DNSError{Err: errNoSuchHost.Error(), Name: name, IsNotFound: true}
}
}
if !isDomainName(name) {
// See comment in func lookup above about use of errNoSuchHost.
return nil, dnsmessage.Name{}, result, &DNSError{Err: errNoSuchHost.Error(), Name: name, IsNotFound: true}
}
type result0 struct {
p dnsmessage.Parser
server string
result Result
error
}
if conf == nil {
conf = getSystemDNSConfig()
}
lane := make(chan result0, 1)
qtypes := []dnsmessage.Type{dnsmessage.TypeA, dnsmessage.TypeAAAA}
if network == "CNAME" {
qtypes = append(qtypes, dnsmessage.TypeCNAME)
}
switch ipVersion(network) {
case '4':
qtypes = []dnsmessage.Type{dnsmessage.TypeA}
case '6':
qtypes = []dnsmessage.Type{dnsmessage.TypeAAAA}
}
var queryFn func(fqdn string, qtype dnsmessage.Type)
var responseFn func(fqdn string, qtype dnsmessage.Type) result0
if conf.singleRequest {
queryFn = func(fqdn string, qtype dnsmessage.Type) {}
responseFn = func(fqdn string, qtype dnsmessage.Type) result0 {
dnsWaitGroup.Add(1)
defer dnsWaitGroup.Done()
p, server, res, err := r.tryOneName(ctx, conf, fqdn, qtype)
return result0{p, server, res, err}
}
} else {
queryFn = func(fqdn string, qtype dnsmessage.Type) {
dnsWaitGroup.Add(1)
go func(qtype dnsmessage.Type) {
p, server, res, err := r.tryOneName(ctx, conf, fqdn, qtype)
lane <- result0{p, server, res, err}
dnsWaitGroup.Done()
}(qtype)
}
responseFn = func(fqdn string, qtype dnsmessage.Type) result0 {
return <-lane
}
}
var lastErr error
var lastResult Result
for _, fqdn := range conf.nameList(name) {
for _, qtype := range qtypes {
queryFn(fqdn, qtype)
}
hitStrictError := false
for _, qtype := range qtypes {
result0 := responseFn(fqdn, qtype)
if result0.error != nil {
if nerr, ok := result0.error.(net.Error); ok && nerr.Temporary() && r.strictErrors() {
// This error will abort the nameList loop.
hitStrictError = true
lastErr = result0.error
lastResult = result0.result
} else if lastErr == nil || fqdn == name+"." {
// Prefer error for original name.
lastErr = result0.error
lastResult = result0.result
}
continue
}
result = result0.result
// Presotto says it's okay to assume that servers listed in
// /etc/resolv.conf are recursive resolvers.
//
// We asked for recursion, so it should have included all the
// answers we need in this one packet.
//
// Further, RFC 1034 section 4.3.1 says that "the recursive
// response to a query will be... The answer to the query,
// possibly preface by one or more CNAME RRs that specify
// aliases encountered on the way to an answer."
//
// Therefore, we should be able to assume that we can ignore
// CNAMEs and that the A and AAAA records we requested are
// for the canonical name.
loop:
for {
h, err := result0.p.AnswerHeader()
if err != nil && err != dnsmessage.ErrSectionDone {
lastErr = &DNSError{
Err: "cannot marshal DNS message",
Name: name,
Server: result0.server,
}
}
if err != nil {
break
}
switch h.Type {
case dnsmessage.TypeA:
a, err := result0.p.AResource()
if err != nil {
lastErr = &DNSError{
Err: "cannot marshal DNS message",
Name: name,
Server: result0.server,
}
break loop
}
addrs = append(addrs, net.IPAddr{IP: net.IP(a.A[:])})
if cname.Length == 0 && h.Name.Length != 0 {
cname = h.Name
}
case dnsmessage.TypeAAAA:
aaaa, err := result0.p.AAAAResource()
if err != nil {
lastErr = &DNSError{
Err: "cannot marshal DNS message",
Name: name,
Server: result0.server,
}
break loop
}
addrs = append(addrs, net.IPAddr{IP: net.IP(aaaa.AAAA[:])})
if cname.Length == 0 && h.Name.Length != 0 {
cname = h.Name
}
case dnsmessage.TypeCNAME:
c, err := result0.p.CNAMEResource()
if err != nil {
lastErr = &DNSError{
Err: "cannot marshal DNS message",
Name: name,
Server: result0.server,
}
break loop
}
if cname.Length == 0 && c.CNAME.Length > 0 {
cname = c.CNAME
}
default:
if err := result0.p.SkipAnswer(); err != nil {
lastErr = &DNSError{
Err: "cannot marshal DNS message",
Name: name,
Server: result0.server,
}
break loop
}
continue
}
}
}
if hitStrictError {
// If either family hit an error with StrictErrors enabled,
// discard all addresses. This ensures that network flakiness
// cannot turn a dualstack hostname IPv4/IPv6-only.
addrs = nil
break
}
if len(addrs) > 0 || network == "CNAME" && cname.Length > 0 {
break
}
}
if lastErr, ok := lastErr.(*DNSError); ok {
// Show original name passed to lookup, not suffixed one.
// In general we might have tried many suffixes; showing
// just one is misleading. See also golang.org/issue/6324.
lastErr.Name = name
}
sortByRFC6724(addrs)
if len(addrs) == 0 && !(network == "CNAME" && cname.Length > 0) {
if order == hostLookupDNSFiles {
var canonical string
addrs, canonical = goLookupIPFiles(name)
if len(addrs) > 0 {
var err error
cname, err = dnsmessage.NewName(canonical)
if err != nil {
return nil, dnsmessage.Name{}, result, err
}
return addrs, cname, result, nil
}
}
if lastErr != nil {
return nil, dnsmessage.Name{}, lastResult, lastErr
}
}
return addrs, cname, result, nil
}
// goLookupCNAME is the native Go (non-cgo) implementation of LookupCNAME.
func (r *Resolver) goLookupCNAME(ctx context.Context, host string, order hostLookupOrder, conf *dnsConfig) (string, Result, error) {
_, cname, result, err := r.goLookupIPCNAMEOrder(ctx, "CNAME", host, order, conf)
return cname.String(), result, err
}
// goLookupPTR is the native Go implementation of LookupAddr.
func (r *Resolver) goLookupPTR(ctx context.Context, addr string, order hostLookupOrder, conf *dnsConfig) ([]string, Result, error) {
if order == hostLookupFiles || order == hostLookupFilesDNS {
names := lookupStaticAddr(addr)
if len(names) > 0 {
return names, Result{}, nil
}
if order == hostLookupFiles {
return nil, Result{}, &DNSError{Err: errNoSuchHost.Error(), Name: addr, IsNotFound: true}
}
}
arpa, err := reverseaddr(addr)
if err != nil {
return nil, Result{}, err
}
p, server, result, err := r.lookup(ctx, arpa, dnsmessage.TypePTR, conf)
if err != nil {
var dnsErr *DNSError
if errors.As(err, &dnsErr) && dnsErr.IsNotFound {
if order == hostLookupDNSFiles {
names := lookupStaticAddr(addr)
if len(names) > 0 {
return names, result, nil
}
}
}
return nil, result, err
}
var ptrs []string
for {
h, err := p.AnswerHeader()
if err == dnsmessage.ErrSectionDone {
break
}
if err != nil {
return nil, result, &DNSError{
Err: "cannot marshal DNS message",
Name: addr,
Server: server,
}
}
if h.Type != dnsmessage.TypePTR {
err := p.SkipAnswer()
if err != nil {
return nil, result, &DNSError{
Err: "cannot marshal DNS message",
Name: addr,
Server: server,
}
}
continue
}
ptr, err := p.PTRResource()
if err != nil {
return nil, result, &DNSError{
Err: "cannot marshal DNS message",
Name: addr,
Server: server,
}
}
ptrs = append(ptrs, ptr.PTR.String())
}
return ptrs, result, nil
}

45
vendor/github.com/mjl-/adns/dnsconfig.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package adns
import (
"os"
"sync/atomic"
"time"
)
var (
defaultNS = []string{"127.0.0.1:53", "[::1]:53"}
getHostname = os.Hostname // variable for testing
)
type dnsConfig struct {
servers []string // server addresses (in host:port form) to use
search []string // rooted suffixes to append to local name
ndots int // number of dots in name to trigger absolute lookup
timeout time.Duration // wait before giving up on a query, including retries
attempts int // lost packets before giving up on server
rotate bool // round robin among servers
unknownOpt bool // anything unknown was encountered
lookup []string // OpenBSD top-level database "lookup" order
err error // any error that occurs during open of resolv.conf
mtime time.Time // time of resolv.conf modification
soffset uint32 // used by serverOffset
singleRequest bool // use sequential A and AAAA queries instead of parallel queries
useTCP bool // force usage of TCP for DNS resolutions
trustAD bool // add AD flag to queries
noReload bool // do not check for config file updates
}
// serverOffset returns an offset that can be used to determine
// indices of servers in c.servers when making queries.
// When the rotate option is enabled, this offset increases.
// Otherwise it is always 0.
func (c *dnsConfig) serverOffset() uint32 {
if c.rotate {
return atomic.AddUint32(&c.soffset, 1) - 1 // return 0 to start
}
return 0
}

188
vendor/github.com/mjl-/adns/dnsconfig_unix.go generated vendored Normal file
View File

@ -0,0 +1,188 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !js && !windows
// Read system DNS config from /etc/resolv.conf
package adns
import (
"net"
"net/netip"
"time"
"github.com/mjl-/adns/internal/bytealg"
)
// See resolv.conf(5) on a Linux machine.
func dnsReadConfig(filename string) *dnsConfig {
conf := &dnsConfig{
ndots: 1,
timeout: 5 * time.Second,
attempts: 2,
}
file, err := open(filename)
if err != nil {
conf.servers = defaultNS
conf.trustAD = true
conf.search = dnsDefaultSearch()
conf.err = err
return conf
}
defer file.close()
if fi, err := file.file.Stat(); err == nil {
conf.mtime = fi.ModTime()
} else {
conf.servers = defaultNS
conf.trustAD = true
conf.search = dnsDefaultSearch()
conf.err = err
return conf
}
for line, ok := file.readLine(); ok; line, ok = file.readLine() {
if len(line) > 0 && (line[0] == ';' || line[0] == '#') {
// comment.
continue
}
f := getFields(line)
if len(f) < 1 {
continue
}
switch f[0] {
case "nameserver": // add one name server
if len(f) > 1 && len(conf.servers) < 3 { // small, but the standard limit
// One more check: make sure server name is
// just an IP address. Otherwise we need DNS
// to look it up.
if _, err := netip.ParseAddr(f[1]); err == nil {
conf.servers = append(conf.servers, JoinHostPort(f[1], "53"))
}
}
case "domain": // set search path to just this domain
if len(f) > 1 {
conf.search = []string{ensureRooted(f[1])}
}
case "search": // set search path to given servers
conf.search = make([]string, 0, len(f)-1)
for i := 1; i < len(f); i++ {
name := ensureRooted(f[i])
if name == "." {
continue
}
conf.search = append(conf.search, name)
}
case "options": // magic options
for _, s := range f[1:] {
switch {
case hasPrefix(s, "ndots:"):
n, _, _ := dtoi(s[6:])
if n < 0 {
n = 0
} else if n > 15 {
n = 15
}
conf.ndots = n
case hasPrefix(s, "timeout:"):
n, _, _ := dtoi(s[8:])
if n < 1 {
n = 1
}
conf.timeout = time.Duration(n) * time.Second
case hasPrefix(s, "attempts:"):
n, _, _ := dtoi(s[9:])
if n < 1 {
n = 1
}
conf.attempts = n
case s == "rotate":
conf.rotate = true
case s == "single-request" || s == "single-request-reopen":
// Linux option:
// http://man7.org/linux/man-pages/man5/resolv.conf.5.html
// "By default, glibc performs IPv4 and IPv6 lookups in parallel [...]
// This option disables the behavior and makes glibc
// perform the IPv6 and IPv4 requests sequentially."
conf.singleRequest = true
case s == "use-vc" || s == "usevc" || s == "tcp":
// Linux (use-vc), FreeBSD (usevc) and OpenBSD (tcp) option:
// http://man7.org/linux/man-pages/man5/resolv.conf.5.html
// "Sets RES_USEVC in _res.options.
// This option forces the use of TCP for DNS resolutions."
// https://www.freebsd.org/cgi/man.cgi?query=resolv.conf&sektion=5&manpath=freebsd-release-ports
// https://man.openbsd.org/resolv.conf.5
conf.useTCP = true
case s == "trust-ad":
conf.trustAD = true
case s == "edns0":
// We use EDNS by default.
// Ignore this option.
case s == "no-reload":
conf.noReload = true
default:
conf.unknownOpt = true
}
}
case "lookup":
// OpenBSD option:
// https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5
// "the legal space-separated values are: bind, file, yp"
conf.lookup = f[1:]
default:
conf.unknownOpt = true
}
}
if len(conf.servers) == 0 {
conf.servers = defaultNS
}
if !conf.trustAD {
// If we only have name servers on loopback IP, we trust them.
// As mentioned in RFC 6698, section A.3 point 2 (line 1693).
conf.trustAD = true
for _, addr := range conf.servers {
host, _, err := net.SplitHostPort(addr)
if err != nil {
conf.trustAD = false
break
}
ip := net.ParseIP(host)
if ip == nil || !ip.IsLoopback() {
conf.trustAD = false
break
}
}
}
if len(conf.search) == 0 {
conf.search = dnsDefaultSearch()
}
return conf
}
func dnsDefaultSearch() []string {
hn, err := getHostname()
if err != nil {
// best effort
return nil
}
if i := bytealg.IndexByteString(hn, '.'); i >= 0 && i < len(hn)-1 {
return []string{ensureRooted(hn[i+1:])}
}
return nil
}
func hasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}
func ensureRooted(s string) string {
if len(s) > 0 && s[len(s)-1] == '.' {
return s
}
return s + "."
}

20
vendor/github.com/mjl-/adns/doc.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
/*
adns is a copy of the Go standard library, modified to provide details about
the DNSSEC status of responses.
The MX, NS, SRV types from the "net" package are used to make to prevent churn
when switching from net to adns.
Modifications
- Each Lookup* also returns a Result with the "Authentic" field representing if
the response had the "authentic data" bit (and is trusted), i.e. was
DNSSEC-signed according to the recursive resolver.
- Resolver are also trusted if all name servers have loopback IPs. Resolvers
are still also trusted if /etc/resolv.conf has "trust-ad" in the "options".
- New function LookupTLSA, to support DANE which uses DNS records of type TLSA.
- Support Extended DNS Errors (EDE) for details about DNSSEC errors.
- adns uses its own DNSError type, with an additional "Underlying error" field
and Unwrap function, so callers can check for the new ExtendedError type.
*/
package adns

168
vendor/github.com/mjl-/adns/ede.go generated vendored Normal file
View File

@ -0,0 +1,168 @@
package adns
import (
"fmt"
)
// ExtendedError is an RFC 8914 Extended DNS Error (EDE).
type ExtendedError struct {
InfoCode ErrorCode
ExtraText string // Human-readable error message, optional.
}
// IsTemporary indicates whether an error is a temporary server error, and
// retries might give a different result.
func (e ExtendedError) IsTemporary() bool {
return e.InfoCode.IsTemporary()
}
// Unwrap returns the underlying ErrorCode error.
func (e ExtendedError) Unwrap() error {
return e.InfoCode
}
// Error returns a string representing the InfoCode, and either the extra text or
// more details for the info code.
func (e ExtendedError) Error() string {
s := e.InfoCode.Error()
if e.ExtraText != "" {
return s + ": " + e.ExtraText
}
if int(e.InfoCode) >= len(errorCodeDetails) {
return s
}
return s + ": " + errorCodeDetails[e.InfoCode]
}
// ErrorCode is an InfoCode from Extended DNS Errors, RFC 8914.
type ErrorCode uint16
const (
ErrOtherErrorCode ErrorCode = 0
ErrUnsupportedDNSKEYAlgorithm ErrorCode = 1
ErrUnsupportedDSDigestType ErrorCode = 2
ErrStaleAnswer ErrorCode = 3
ErrForgedAnswer ErrorCode = 4
ErrDNSSECIndeterminate ErrorCode = 5
ErrDNSSECBogus ErrorCode = 6
ErrSignatureExpired ErrorCode = 7
ErrSignatureNotYetValid ErrorCode = 8
ErrDNSKEYMissing ErrorCode = 9
ErrRRSIGMissing ErrorCode = 10
ErrNoZoneKeyBitSet ErrorCode = 11
ErrNSECMissing ErrorCode = 12
ErrCachedError ErrorCode = 13
ErrNotReady ErrorCode = 14
ErrBlocked ErrorCode = 15
ErrCensored ErrorCode = 16
ErrFiltered ErrorCode = 17
ErrProhibited ErrorCode = 18
ErrStaleNXDOMAINAnswer ErrorCode = 19
ErrNotAuthoritative ErrorCode = 20
ErrNotSupported ErrorCode = 21
ErrNoReachableAuthority ErrorCode = 22
ErrNetworkError ErrorCode = 23
ErrInvalidData ErrorCode = 24
)
// IsTemporary returns whether the error is temporary and has a chance of
// succeeding on a retry.
func (e ErrorCode) IsTemporary() bool {
switch e {
case ErrOtherErrorCode,
ErrStaleAnswer,
ErrCachedError,
ErrNotReady,
ErrStaleNXDOMAINAnswer,
ErrNoReachableAuthority,
ErrNetworkError:
return true
}
return false
}
// IsAuthentication returns whether the error is related to authentication,
// e.g. bogus DNSSEC, missing DS/DNSKEY/RRSIG records, etc, or an other
// DNSSEC-related error.
func (e ErrorCode) IsAuthentication() bool {
switch e {
case ErrUnsupportedDNSKEYAlgorithm,
ErrUnsupportedDSDigestType,
ErrDNSSECIndeterminate,
ErrDNSSECBogus,
ErrSignatureExpired,
ErrSignatureNotYetValid,
ErrDNSKEYMissing,
ErrRRSIGMissing,
ErrNoZoneKeyBitSet,
ErrNSECMissing:
return true
}
return false
}
// Error includes a human-readable short string for the info code.
func (e ErrorCode) Error() string {
if int(e) >= len(errorCodeStrings) {
return fmt.Sprintf("unknown error code from name server: %d", e)
}
return fmt.Sprintf("error from name server: %s", errorCodeStrings[e])
}
// short strings, always included in error messages.
var errorCodeStrings = []string{
"other",
"unsupported dnskey algorithm",
"unsupported ds digest type",
"stale answer",
"forged answer",
"dnssec indeterminate",
"dnssec bogus",
"signature expired",
"signature not yet valid",
"dnskey missing",
"rrsigs missing",
"no zone key bit set",
"nsec missing",
"cached error",
"not ready",
"blocked",
"censored",
"filtered",
"prohibited",
"stale nxdomain answer",
"not authoritative",
"not supported",
"no reachable authority",
"network error",
"invalid data",
}
// more detailed string, only included if there is no detail text in the response.
var errorCodeDetails = []string{
"unspecified error",
"only found unsupported algorithms in DNSKEY records",
"only found unsupported types in DS records",
"unable to resolve within deadline, stale data served",
"answer was forged for policy reason",
"dnssec validation ended in interderminate state",
"dnssec validation ended in bogus status",
"only expired dnssec signatures found",
"only signatures found that are not yet valid",
"ds key exists at a parent, but no supported matching dnskey found",
"dnssec validation attempted, but no rrsig found",
"no zone key bit found in a dnskey",
"dnssec validation found missing data without nsec/nsec3 record",
"failure served from cache",
"not yet fully functional to resolve query",
"domain is on blocklist due to internal security policy",
"domain is on blocklist due to external entity",
"domain is on client-requested blocklist",
"refusing to serve request",
"stale nxdomain served from cache",
"unexpected authoritativeness of query",
"query or operation not supported",
"no authoritative name server could be reached",
"unrecoverable network error",
"zone data not valid",
}

22
vendor/github.com/mjl-/adns/hook.go generated vendored Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package adns
import (
"context"
"net"
)
var (
testHookHostsPath = "/etc/hosts"
testHookLookupIP = func(
ctx context.Context,
fn func(context.Context, string, string) ([]net.IPAddr, Result, error),
network string,
host string,
) ([]net.IPAddr, Result, error) {
return fn(ctx, network, host)
}
)

166
vendor/github.com/mjl-/adns/hosts.go generated vendored Normal file
View File

@ -0,0 +1,166 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package adns
import (
"errors"
"io/fs"
"net/netip"
"sync"
"time"
"github.com/mjl-/adns/internal/bytealg"
)
const cacheMaxAge = 5 * time.Second
func parseLiteralIP(addr string) string {
ip, err := netip.ParseAddr(addr)
if err != nil {
return ""
}
return ip.String()
}
type byName struct {
addrs []string
canonicalName string
}
// hosts contains known host entries.
var hosts struct {
sync.Mutex
// Key for the list of literal IP addresses must be a host
// name. It would be part of DNS labels, a FQDN or an absolute
// FQDN.
// For now the key is converted to lower case for convenience.
byName map[string]byName
// Key for the list of host names must be a literal IP address
// including IPv6 address with zone identifier.
// We don't support old-classful IP address notation.
byAddr map[string][]string
expire time.Time
path string
mtime time.Time
size int64
}
func readHosts() {
now := time.Now()
hp := testHookHostsPath
if now.Before(hosts.expire) && hosts.path == hp && len(hosts.byName) > 0 {
return
}
mtime, size, err := stat(hp)
if err == nil && hosts.path == hp && hosts.mtime.Equal(mtime) && hosts.size == size {
hosts.expire = now.Add(cacheMaxAge)
return
}
hs := make(map[string]byName)
is := make(map[string][]string)
file, err := open(hp)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, fs.ErrPermission) {
return
}
}
if file != nil {
defer file.close()
for line, ok := file.readLine(); ok; line, ok = file.readLine() {
if i := bytealg.IndexByteString(line, '#'); i >= 0 {
// Discard comments.
line = line[0:i]
}
f := getFields(line)
if len(f) < 2 {
continue
}
addr := parseLiteralIP(f[0])
if addr == "" {
continue
}
var canonical string
for i := 1; i < len(f); i++ {
name := absDomainName(f[i])
h := []byte(f[i])
lowerASCIIBytes(h)
key := absDomainName(string(h))
if i == 1 {
canonical = key
}
is[addr] = append(is[addr], name)
if v, ok := hs[key]; ok {
hs[key] = byName{
addrs: append(v.addrs, addr),
canonicalName: v.canonicalName,
}
continue
}
hs[key] = byName{
addrs: []string{addr},
canonicalName: canonical,
}
}
}
}
// Update the data cache.
hosts.expire = now.Add(cacheMaxAge)
hosts.path = hp
hosts.byName = hs
hosts.byAddr = is
hosts.mtime = mtime
hosts.size = size
}
// lookupStaticHost looks up the addresses and the canonical name for the given host from /etc/hosts.
func lookupStaticHost(host string) ([]string, string) {
hosts.Lock()
defer hosts.Unlock()
readHosts()
if len(hosts.byName) != 0 {
if hasUpperCase(host) {
lowerHost := []byte(host)
lowerASCIIBytes(lowerHost)
host = string(lowerHost)
}
if byName, ok := hosts.byName[absDomainName(host)]; ok {
ipsCp := make([]string, len(byName.addrs))
copy(ipsCp, byName.addrs)
return ipsCp, byName.canonicalName
}
}
return nil, ""
}
// lookupStaticAddr looks up the hosts for the given address from /etc/hosts.
func lookupStaticAddr(addr string) []string {
hosts.Lock()
defer hosts.Unlock()
readHosts()
addr = parseLiteralIP(addr)
if addr == "" {
return nil
}
if len(hosts.byAddr) != 0 {
if hosts, ok := hosts.byAddr[addr]; ok {
hostsCp := make([]string, len(hosts))
copy(hostsCp, hosts)
return hostsCp
}
}
return nil
}

View File

@ -0,0 +1,9 @@
package bytealg
import (
"strings"
)
func IndexByteString(s string, b byte) int {
return strings.IndexByte(s, b)
}

33
vendor/github.com/mjl-/adns/internal/itoa/itoa.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Simple conversions to avoid depending on strconv.
package itoa
// Itoa converts val to a decimal string.
func Itoa(val int) string {
if val < 0 {
return "-" + Uitoa(uint(-val))
}
return Uitoa(uint(val))
}
// Uitoa converts val to a decimal string.
func Uitoa(val uint) string {
if val == 0 { // avoid string allocation
return "0"
}
var buf [20]byte // big enough for 64bit value base 10
i := len(buf) - 1
for val >= 10 {
q := val / 10
buf[i] = byte('0' + val - q*10)
i--
val = q
}
// val < 10
buf[i] = byte('0' + val)
return string(buf[i:])
}

View File

@ -0,0 +1,123 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package singleflight provides a duplicate function call suppression
// mechanism.
package singleflight
import "sync"
// call is an in-flight or completed singleflight.Do call
type call struct {
wg sync.WaitGroup
// These fields are written once before the WaitGroup is done
// and are only read after the WaitGroup is done.
val any
err error
// These fields are read and written with the singleflight
// mutex held before the WaitGroup is done, and are read but
// not written after the WaitGroup is done.
dups int
chans []chan<- Result
}
// Group represents a class of work and forms a namespace in
// which units of work can be executed with duplicate suppression.
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
}
// Result holds the results of Do, so they can be passed
// on a channel.
type Result struct {
Val any
Err error
Shared bool
}
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
// The return value shared indicates whether v was given to multiple callers.
func (g *Group) Do(key string, fn func() (any, error)) (v any, err error, shared bool) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
if c, ok := g.m[key]; ok {
c.dups++
g.mu.Unlock()
c.wg.Wait()
return c.val, c.err, true
}
c := new(call)
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
g.doCall(c, key, fn)
return c.val, c.err, c.dups > 0
}
// DoChan is like Do but returns a channel that will receive the
// results when they are ready.
func (g *Group) DoChan(key string, fn func() (any, error)) <-chan Result {
ch := make(chan Result, 1)
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
if c, ok := g.m[key]; ok {
c.dups++
c.chans = append(c.chans, ch)
g.mu.Unlock()
return ch
}
c := &call{chans: []chan<- Result{ch}}
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
go g.doCall(c, key, fn)
return ch
}
// doCall handles the single call for a key.
func (g *Group) doCall(c *call, key string, fn func() (any, error)) {
c.val, c.err = fn()
g.mu.Lock()
c.wg.Done()
if g.m[key] == c {
delete(g.m, key)
}
for _, ch := range c.chans {
ch <- Result{c.val, c.err, c.dups > 0}
}
g.mu.Unlock()
}
// ForgetUnshared tells the singleflight to forget about a key if it is not
// shared with any other goroutines. Future calls to Do for a forgotten key
// will call the function rather than waiting for an earlier call to complete.
// Returns whether the key was forgotten or unknown--that is, whether no
// other goroutines are waiting for the result.
func (g *Group) ForgetUnshared(key string) bool {
g.mu.Lock()
defer g.mu.Unlock()
c, ok := g.m[key]
if !ok {
return true
}
if c.dups == 0 {
delete(g.m, key)
return true
}
return false
}

205
vendor/github.com/mjl-/adns/ipsock.go generated vendored Normal file
View File

@ -0,0 +1,205 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package adns
import (
"context"
"net"
"github.com/mjl-/adns/internal/bytealg"
)
// BUG(rsc,mikio): On DragonFly BSD and OpenBSD, listening on the
// "tcp" and "udp" networks does not listen for both IPv4 and IPv6
// connections. This is due to the fact that IPv4 traffic will not be
// routed to an IPv6 socket - two separate sockets are required if
// both address families are to be supported.
// See inet6(4) for details.
// An addrList represents a list of network endpoint addresses.
type addrList []net.Addr
// filterAddrList applies a filter to a list of IP addresses,
// yielding a list of Addr objects. Known filters are nil, ipv4only,
// and ipv6only. It returns every address when the filter is nil.
// The result contains at least one address when error is nil.
func filterAddrList(filter func(net.IPAddr) bool, ips []net.IPAddr, inetaddr func(net.IPAddr) net.Addr, originalAddr string) (addrList, error) {
var addrs addrList
for _, ip := range ips {
if filter == nil || filter(ip) {
addrs = append(addrs, inetaddr(ip))
}
}
if len(addrs) == 0 {
return nil, &net.AddrError{Err: errNoSuitableAddress.Error(), Addr: originalAddr}
}
return addrs, nil
}
// ipv4only reports whether addr is an IPv4 address.
func ipv4only(addr net.IPAddr) bool {
return addr.IP.To4() != nil
}
// ipv6only reports whether addr is an IPv6 address except IPv4-mapped IPv6 address.
func ipv6only(addr net.IPAddr) bool {
return len(addr.IP) == net.IPv6len && addr.IP.To4() == nil
}
// SplitHostPort splits a network address of the form "host:port",
// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or
// host%zone and port.
//
// A literal IPv6 address in hostport must be enclosed in square
// brackets, as in "[::1]:80", "[::1%lo0]:80".
//
// See func Dial for a description of the hostport parameter, and host
// and port results.
func SplitHostPort(hostport string) (host, port string, err error) {
const (
missingPort = "missing port in address"
tooManyColons = "too many colons in address"
)
addrErr := func(addr, why string) (host, port string, err error) {
return "", "", &net.AddrError{Err: why, Addr: addr}
}
j, k := 0, 0
// The port starts after the last colon.
i := last(hostport, ':')
if i < 0 {
return addrErr(hostport, missingPort)
}
if hostport[0] == '[' {
// Expect the first ']' just before the last ':'.
end := bytealg.IndexByteString(hostport, ']')
if end < 0 {
return addrErr(hostport, "missing ']' in address")
}
switch end + 1 {
case len(hostport):
// There can't be a ':' behind the ']' now.
return addrErr(hostport, missingPort)
case i:
// The expected result.
default:
// Either ']' isn't followed by a colon, or it is
// followed by a colon that is not the last one.
if hostport[end+1] == ':' {
return addrErr(hostport, tooManyColons)
}
return addrErr(hostport, missingPort)
}
host = hostport[1:end]
j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions
} else {
host = hostport[:i]
if bytealg.IndexByteString(host, ':') >= 0 {
return addrErr(hostport, tooManyColons)
}
}
if bytealg.IndexByteString(hostport[j:], '[') >= 0 {
return addrErr(hostport, "unexpected '[' in address")
}
if bytealg.IndexByteString(hostport[k:], ']') >= 0 {
return addrErr(hostport, "unexpected ']' in address")
}
port = hostport[i+1:]
return host, port, nil
}
func splitHostZone(s string) (host, zone string) {
// The IPv6 scoped addressing zone identifier starts after the
// last percent sign.
if i := last(s, '%'); i > 0 {
host, zone = s[:i], s[i+1:]
} else {
host = s
}
return
}
// JoinHostPort combines host and port into a network address of the
// form "host:port". If host contains a colon, as found in literal
// IPv6 addresses, then JoinHostPort returns "[host]:port".
//
// See func Dial for a description of the host and port parameters.
func JoinHostPort(host, port string) string {
// We assume that host is a literal IPv6 address if host has
// colons.
if bytealg.IndexByteString(host, ':') >= 0 {
return "[" + host + "]:" + port
}
return host + ":" + port
}
// internetAddrList resolves addr, which may be a literal IP
// address or a DNS name, and returns a list of internet protocol
// family addresses. The result contains at least one address when
// error is nil.
func (r *Resolver) internetAddrList(ctx context.Context, network, addr string) (addrList, Result, error) {
var (
err error
host, port string
portnum int
)
switch network {
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
if addr != "" {
if host, port, err = SplitHostPort(addr); err != nil {
return nil, Result{}, err
}
if portnum, err = r.LookupPort(ctx, network, port); err != nil {
return nil, Result{}, err
}
}
case "ip", "ip4", "ip6":
if addr != "" {
host = addr
}
default:
return nil, Result{}, net.UnknownNetworkError(network)
}
inetaddr := func(ip net.IPAddr) net.Addr {
switch network {
case "tcp", "tcp4", "tcp6":
return &net.TCPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone}
case "udp", "udp4", "udp6":
return &net.UDPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone}
case "ip", "ip4", "ip6":
return &net.IPAddr{IP: ip.IP, Zone: ip.Zone}
default:
panic("unexpected network: " + network)
}
}
if host == "" {
return addrList{inetaddr(net.IPAddr{})}, Result{}, nil
}
// Try as a literal IP address, then as a DNS name.
ips, result, err := r.lookupIPAddr(ctx, network, host)
if err != nil {
return nil, result, err
}
// Issue 18806: if the machine has halfway configured
// IPv6 such that it can bind on "::" (IPv6unspecified)
// but not connect back to that same address, fall
// back to dialing 0.0.0.0.
if len(ips) == 1 && ips[0].IP.Equal(net.IPv6unspecified) {
ips = append(ips, net.IPAddr{IP: net.IPv4zero})
}
var filter func(net.IPAddr) bool
if network != "" && network[len(network)-1] == '4' {
filter = ipv4only
}
if network != "" && network[len(network)-1] == '6' {
filter = ipv6only
}
addrs, err := filterAddrList(filter, ips, inetaddr, host)
return addrs, result, err
}

973
vendor/github.com/mjl-/adns/lookup.go generated vendored Normal file
View File

@ -0,0 +1,973 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package adns
import (
"context"
"fmt"
"net"
"net/netip"
"sync"
"golang.org/x/net/dns/dnsmessage"
"github.com/mjl-/adns/internal/singleflight"
)
// protocols contains minimal mappings between internet protocol
// names and numbers for platforms that don't have a complete list of
// protocol numbers.
//
// See https://www.iana.org/assignments/protocol-numbers
//
// On Unix, this map is augmented by readProtocols via lookupProtocol.
var protocols = map[string]int{
"icmp": 1,
"igmp": 2,
"tcp": 6,
"udp": 17,
"ipv6-icmp": 58,
}
// services contains minimal mappings between services names and port
// numbers for platforms that don't have a complete list of port numbers.
//
// See https://www.iana.org/assignments/service-names-port-numbers
//
// On Unix, this map is augmented by readServices via goLookupPort.
var services = map[string]map[string]int{
"udp": {
"domain": 53,
},
"tcp": {
"ftp": 21,
"ftps": 990,
"gopher": 70, // ʕ◔ϖ◔ʔ
"http": 80,
"https": 443,
"imap2": 143,
"imap3": 220,
"imaps": 993,
"pop3": 110,
"pop3s": 995,
"smtp": 25,
"ssh": 22,
"telnet": 23,
},
}
// dnsWaitGroup can be used by tests to wait for all DNS goroutines to
// complete. This avoids races on the test hooks.
var dnsWaitGroup sync.WaitGroup
const maxProtoLength = len("RSVP-E2E-IGNORE") + 10 // with room to grow
func lookupProtocolMap(name string) (int, error) {
var lowerProtocol [maxProtoLength]byte
n := copy(lowerProtocol[:], name)
lowerASCIIBytes(lowerProtocol[:n])
proto, found := protocols[string(lowerProtocol[:n])]
if !found || n != len(name) {
return 0, &net.AddrError{Err: "unknown IP protocol specified", Addr: name}
}
return proto, nil
}
// maxPortBufSize is the longest reasonable name of a service
// (non-numeric port).
// Currently the longest known IANA-unregistered name is
// "mobility-header", so we use that length, plus some slop in case
// something longer is added in the future.
const maxPortBufSize = len("mobility-header") + 10
func lookupPortMap(network, service string) (port int, error error) {
switch network {
case "tcp4", "tcp6":
network = "tcp"
case "udp4", "udp6":
network = "udp"
}
if m, ok := services[network]; ok {
var lowerService [maxPortBufSize]byte
n := copy(lowerService[:], service)
lowerASCIIBytes(lowerService[:n])
if port, ok := m[string(lowerService[:n])]; ok && n == len(service) {
return port, nil
}
}
return 0, &net.AddrError{Err: "unknown port", Addr: network + "/" + service}
}
// ipVersion returns the provided network's IP version: '4', '6' or 0
// if network does not end in a '4' or '6' byte.
func ipVersion(network string) byte {
if network == "" {
return 0
}
n := network[len(network)-1]
if n != '4' && n != '6' {
n = 0
}
return n
}
// DefaultResolver is the resolver used by the package-level Lookup
// functions and by Dialers without a specified Resolver.
var DefaultResolver = &Resolver{}
// A Resolver looks up names and numbers.
//
// A nil *Resolver is equivalent to a zero Resolver.
type Resolver struct {
// PreferGo controls whether Go's built-in DNS resolver is preferred
// on platforms where it's available. It is equivalent to setting
// GODEBUG=netdns=go, but scoped to just this resolver.
PreferGo bool
// StrictErrors controls the behavior of temporary errors
// (including timeout, socket errors, and SERVFAIL) when using
// Go's built-in resolver. For a query composed of multiple
// sub-queries (such as an A+AAAA address lookup, or walking the
// DNS search list), this option causes such errors to abort the
// whole query instead of returning a partial result. This is
// not enabled by default because it may affect compatibility
// with resolvers that process AAAA queries incorrectly.
StrictErrors bool
// Dial optionally specifies an alternate dialer for use by
// Go's built-in DNS resolver to make TCP and UDP connections
// to DNS services. The host in the address parameter will
// always be a literal IP address and not a host name, and the
// port in the address parameter will be a literal port number
// and not a service name.
// If the Conn returned is also a PacketConn, sent and received DNS
// messages must adhere to RFC 1035 section 4.2.1, "UDP usage".
// Otherwise, DNS messages transmitted over Conn must adhere
// to RFC 7766 section 5, "Transport Protocol Selection".
// If nil, the default dialer is used.
Dial func(ctx context.Context, network, address string) (net.Conn, error)
// lookupGroup merges LookupIPAddr calls together for lookups for the same
// host. The lookupGroup key is the LookupIPAddr.host argument.
// The return values are ([]IPAddr, error).
lookupGroup singleflight.Group
// TODO(bradfitz): optional interface impl override hook
// TODO(bradfitz): Timeout time.Duration?
}
func (r *Resolver) preferGo() bool { return r != nil && r.PreferGo }
func (r *Resolver) strictErrors() bool { return r != nil && r.StrictErrors }
func (r *Resolver) getLookupGroup() *singleflight.Group {
if r == nil {
return &DefaultResolver.lookupGroup
}
return &r.lookupGroup
}
// LookupHost looks up the given host using the local resolver.
// It returns a slice of that host's addresses.
//
// LookupHost uses context.Background internally; to specify the context, use
// Resolver.LookupHost.
func LookupHost(host string) (addrs []string, result Result, err error) {
return DefaultResolver.LookupHost(context.Background(), host)
}
// LookupHost looks up the given host using the local resolver.
// It returns a slice of that host's addresses.
func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, result Result, err error) {
// Make sure that no matter what we do later, host=="" is rejected.
if host == "" {
return nil, result, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
}
if _, err := netip.ParseAddr(host); err == nil {
return []string{host}, result, nil
}
return r.lookupHost(ctx, host)
}
// LookupIP looks up host using the local resolver.
// It returns a slice of that host's IPv4 and IPv6 addresses.
func LookupIP(host string) ([]net.IP, Result, error) {
addrs, result, err := DefaultResolver.LookupIPAddr(context.Background(), host)
if err != nil {
return nil, result, err
}
ips := make([]net.IP, len(addrs))
for i, ia := range addrs {
ips[i] = ia.IP
}
return ips, result, nil
}
// LookupIPAddr looks up host using the local resolver.
// It returns a slice of that host's IPv4 and IPv6 addresses.
func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, Result, error) {
return r.lookupIPAddr(ctx, "ip", host)
}
// LookupIP looks up host for the given network using the local resolver.
// It returns a slice of that host's IP addresses of the type specified by
// network.
// network must be one of "ip", "ip4" or "ip6".
func (r *Resolver) LookupIP(ctx context.Context, network, host string) ([]net.IP, Result, error) {
afnet, _, err := parseNetwork(ctx, network, false)
if err != nil {
return nil, Result{}, err
}
switch afnet {
case "ip", "ip4", "ip6":
default:
return nil, Result{}, net.UnknownNetworkError(network)
}
if host == "" {
return nil, Result{}, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
}
addrs, result, err := r.internetAddrList(ctx, afnet, host)
if err != nil {
return nil, result, err
}
ips := make([]net.IP, 0, len(addrs))
for _, addr := range addrs {
ips = append(ips, addr.(*net.IPAddr).IP)
}
return ips, result, nil
}
// LookupNetIP looks up host using the local resolver.
// It returns a slice of that host's IP addresses of the type specified by
// network.
// The network must be one of "ip", "ip4" or "ip6".
func (r *Resolver) LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, Result, error) {
// TODO(bradfitz): make this efficient, making the internal net package
// type throughout be netip.Addr and only converting to the net.IP slice
// version at the edge. But for now (2021-10-20), this is a wrapper around
// the old way.
ips, result, err := r.LookupIP(ctx, network, host)
if err != nil {
return nil, result, err
}
ret := make([]netip.Addr, 0, len(ips))
for _, ip := range ips {
if a, ok := netip.AddrFromSlice(ip); ok {
ret = append(ret, a)
}
}
return ret, result, nil
}
// onlyValuesCtx is a context that uses an underlying context
// for value lookup if the underlying context hasn't yet expired.
type onlyValuesCtx struct {
context.Context
lookupValues context.Context
}
var _ context.Context = (*onlyValuesCtx)(nil)
// Value performs a lookup if the original context hasn't expired.
func (ovc *onlyValuesCtx) Value(key any) any {
select {
case <-ovc.lookupValues.Done():
return nil
default:
return ovc.lookupValues.Value(key)
}
}
// withUnexpiredValuesPreserved returns a context.Context that only uses lookupCtx
// for its values, otherwise it is never canceled and has no deadline.
// If the lookup context expires, any looked up values will return nil.
// See Issue 28600.
func withUnexpiredValuesPreserved(lookupCtx context.Context) context.Context {
return &onlyValuesCtx{Context: context.Background(), lookupValues: lookupCtx}
}
// lookupIPAddr looks up host using the local resolver and particular network.
// It returns a slice of that host's IPv4 and IPv6 addresses.
func (r *Resolver) lookupIPAddr(ctx context.Context, network, host string) ([]net.IPAddr, Result, error) {
// Make sure that no matter what we do later, host=="" is rejected.
if host == "" {
return nil, Result{}, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
}
if ip, err := netip.ParseAddr(host); err == nil {
return []net.IPAddr{{IP: net.IP(ip.AsSlice()).To16(), Zone: ip.Zone()}}, Result{}, nil
}
// The underlying resolver func is lookupIP by default but it
// can be overridden by tests. This is needed by net/http, so it
// uses a context key instead of unexported variables.
resolverFunc := r.lookupIP
// We don't want a cancellation of ctx to affect the
// lookupGroup operation. Otherwise if our context gets
// canceled it might cause an error to be returned to a lookup
// using a completely different context. However we need to preserve
// only the values in context. See Issue 28600.
lookupGroupCtx, lookupGroupCancel := context.WithCancel(withUnexpiredValuesPreserved(ctx))
type Tuple struct {
ips []net.IPAddr
result Result
}
lookupKey := network + "\000" + host
dnsWaitGroup.Add(1)
ch := r.getLookupGroup().DoChan(lookupKey, func() (any, error) {
ips, result, err := testHookLookupIP(lookupGroupCtx, resolverFunc, network, host)
return Tuple{ips, result}, err
})
dnsWaitGroupDone := func(ch <-chan singleflight.Result, cancelFn context.CancelFunc) {
<-ch
dnsWaitGroup.Done()
cancelFn()
}
select {
case <-ctx.Done():
// Our context was canceled. If we are the only
// goroutine looking up this key, then drop the key
// from the lookupGroup and cancel the lookup.
// If there are other goroutines looking up this key,
// let the lookup continue uncanceled, and let later
// lookups with the same key share the result.
// See issues 8602, 20703, 22724.
if r.getLookupGroup().ForgetUnshared(lookupKey) {
lookupGroupCancel()
go dnsWaitGroupDone(ch, func() {})
} else {
go dnsWaitGroupDone(ch, lookupGroupCancel)
}
ctxErr := ctx.Err()
err := &DNSError{
Err: mapErr(ctxErr).Error(),
Name: host,
IsTimeout: ctxErr == context.DeadlineExceeded,
}
return nil, Result{}, err
case r := <-ch:
dnsWaitGroup.Done()
lookupGroupCancel()
err := r.Err
if err != nil {
if _, ok := err.(*DNSError); !ok {
isTimeout := false
if err == context.DeadlineExceeded {
isTimeout = true
} else if terr, ok := err.(timeout); ok {
isTimeout = terr.Timeout()
}
err = &DNSError{
Err: err.Error(),
Name: host,
IsTimeout: isTimeout,
}
}
}
tuple := r.Val.(Tuple)
if err != nil {
return nil, tuple.result, err
}
ips := lookupIPReturn(tuple.ips, r.Shared)
return ips, tuple.result, nil
}
}
// lookupIPReturn turns the return values from singleflight.Do into
// the return values from LookupIP.
func lookupIPReturn(addrs []net.IPAddr, shared bool) []net.IPAddr {
if shared {
clone := make([]net.IPAddr, len(addrs))
copy(clone, addrs)
addrs = clone
}
return addrs
}
// LookupPort looks up the port for the given network and service.
//
// LookupPort uses context.Background internally; to specify the context, use
// Resolver.LookupPort.
func LookupPort(network, service string) (port int, err error) {
return DefaultResolver.LookupPort(context.Background(), network, service)
}
// LookupPort looks up the port for the given network and service.
func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error) {
port, needsLookup := parsePort(service)
if needsLookup {
switch network {
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
case "": // a hint wildcard for Go 1.0 undocumented behavior
network = "ip"
default:
return 0, &net.AddrError{Err: "unknown network", Addr: network}
}
port, err = r.lookupPort(ctx, network, service)
if err != nil {
return 0, err
}
}
if 0 > port || port > 65535 {
return 0, &net.AddrError{Err: "invalid port", Addr: service}
}
return port, nil
}
// LookupCNAME returns the canonical name for the given host.
// Callers that do not care about the canonical name can call
// LookupHost or LookupIP directly; both take care of resolving
// the canonical name as part of the lookup.
//
// A canonical name is the final name after following zero
// or more CNAME records.
// LookupCNAME does not return an error if host does not
// contain DNS "CNAME" records, as long as host resolves to
// address records.
//
// The returned canonical name is validated to be a properly
// formatted presentation-format domain name.
//
// LookupCNAME uses context.Background internally; to specify the context, use
// Resolver.LookupCNAME.
func LookupCNAME(host string) (cname string, result Result, err error) {
return DefaultResolver.LookupCNAME(context.Background(), host)
}
// LookupCNAME returns the canonical name for the given host.
// Callers that do not care about the canonical name can call
// LookupHost or LookupIP directly; both take care of resolving
// the canonical name as part of the lookup.
//
// A canonical name is the final name after following zero
// or more CNAME records.
// LookupCNAME does not return an error if host does not
// contain DNS "CNAME" records, as long as host resolves to
// address records.
//
// The returned canonical name is validated to be a properly
// formatted presentation-format domain name.
func (r *Resolver) LookupCNAME(ctx context.Context, host string) (string, Result, error) {
cname, result, err := r.lookupCNAME(ctx, host)
if err != nil {
return "", result, err
}
if !isDomainName(cname) {
return "", result, &DNSError{Err: errMalformedDNSRecordsDetail, Name: host}
}
return cname, result, nil
}
// LookupSRV tries to resolve an SRV query of the given service,
// protocol, and domain name. The proto is "tcp" or "udp".
// The returned records are sorted by priority and randomized
// by weight within a priority.
//
// LookupSRV constructs the DNS name to look up following RFC 2782.
// That is, it looks up _service._proto.name. To accommodate services
// publishing SRV records under non-standard names, if both service
// and proto are empty strings, LookupSRV looks up name directly.
//
// The returned service names are validated to be properly
// formatted presentation-format domain names. If the response contains
// invalid names, those records are filtered out and an error
// will be returned alongside the remaining results, if any.
func LookupSRV(service, proto, name string) (cname string, addrs []*net.SRV, result Result, err error) {
return DefaultResolver.LookupSRV(context.Background(), service, proto, name)
}
// LookupSRV tries to resolve an SRV query of the given service,
// protocol, and domain name. The proto is "tcp" or "udp".
// The returned records are sorted by priority and randomized
// by weight within a priority.
//
// LookupSRV constructs the DNS name to look up following RFC 2782.
// That is, it looks up _service._proto.name. To accommodate services
// publishing SRV records under non-standard names, if both service
// and proto are empty strings, LookupSRV looks up name directly.
//
// The returned service names are validated to be properly
// formatted presentation-format domain names. If the response contains
// invalid names, those records are filtered out and an error
// will be returned alongside the remaining results, if any.
func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, Result, error) {
cname, addrs, result, err := r.lookupSRV(ctx, service, proto, name)
if err != nil {
return "", nil, result, err
}
if cname != "" && !isDomainName(cname) {
return "", nil, result, &DNSError{Err: "SRV header name is invalid", Name: name}
}
filteredAddrs := make([]*net.SRV, 0, len(addrs))
for _, addr := range addrs {
if addr == nil {
continue
}
if !isDomainName(addr.Target) {
continue
}
filteredAddrs = append(filteredAddrs, addr)
}
if len(addrs) != len(filteredAddrs) {
return cname, filteredAddrs, result, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name}
}
return cname, filteredAddrs, result, nil
}
// LookupMX returns the DNS MX records for the given domain name sorted by preference.
//
// The returned mail server names are validated to be properly
// formatted presentation-format domain names. If the response contains
// invalid names, those records are filtered out and an error
// will be returned alongside the remaining results, if any.
//
// LookupMX uses context.Background internally; to specify the context, use
// Resolver.LookupMX.
func LookupMX(name string) ([]*net.MX, Result, error) {
return DefaultResolver.LookupMX(context.Background(), name)
}
// LookupMX returns the DNS MX records for the given domain name sorted by preference.
//
// The returned mail server names are validated to be properly
// formatted presentation-format domain names. If the response contains
// invalid names, those records are filtered out and an error
// will be returned alongside the remaining results, if any.
func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*net.MX, Result, error) {
records, result, err := r.lookupMX(ctx, name)
if err != nil {
return nil, result, err
}
filteredMX := make([]*net.MX, 0, len(records))
for _, mx := range records {
if mx == nil {
continue
}
if !isDomainName(mx.Host) {
continue
}
filteredMX = append(filteredMX, mx)
}
if len(records) != len(filteredMX) {
return filteredMX, result, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name}
}
return filteredMX, result, nil
}
// LookupNS returns the DNS NS records for the given domain name.
//
// The returned name server names are validated to be properly
// formatted presentation-format domain names. If the response contains
// invalid names, those records are filtered out and an error
// will be returned alongside the remaining results, if any.
//
// LookupNS uses context.Background internally; to specify the context, use
// Resolver.LookupNS.
func LookupNS(name string) ([]*net.NS, Result, error) {
return DefaultResolver.LookupNS(context.Background(), name)
}
// LookupNS returns the DNS NS records for the given domain name.
//
// The returned name server names are validated to be properly
// formatted presentation-format domain names. If the response contains
// invalid names, those records are filtered out and an error
// will be returned alongside the remaining results, if any.
func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*net.NS, Result, error) {
records, result, err := r.lookupNS(ctx, name)
if err != nil {
return nil, result, err
}
filteredNS := make([]*net.NS, 0, len(records))
for _, ns := range records {
if ns == nil {
continue
}
if !isDomainName(ns.Host) {
continue
}
filteredNS = append(filteredNS, ns)
}
if len(records) != len(filteredNS) {
return filteredNS, result, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name}
}
return filteredNS, result, nil
}
// LookupTXT returns the DNS TXT records for the given domain name.
//
// LookupTXT uses context.Background internally; to specify the context, use
// Resolver.LookupTXT.
func LookupTXT(name string) ([]string, Result, error) {
return DefaultResolver.lookupTXT(context.Background(), name)
}
// LookupTXT returns the DNS TXT records for the given domain name.
func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, Result, error) {
return r.lookupTXT(ctx, name)
}
// LookupAddr performs a reverse lookup for the given address, returning a list
// of names mapping to that address.
//
// The returned names are validated to be properly formatted presentation-format
// domain names. If the response contains invalid names, those records are filtered
// out and an error will be returned alongside the remaining results, if any.
//
// When using the host C library resolver, at most one result will be
// returned. To bypass the host resolver, use a custom Resolver.
//
// LookupAddr uses context.Background internally; to specify the context, use
// Resolver.LookupAddr.
func LookupAddr(addr string) (names []string, result Result, err error) {
return DefaultResolver.LookupAddr(context.Background(), addr)
}
// LookupAddr performs a reverse lookup for the given address, returning a list
// of names mapping to that address.
//
// The returned names are validated to be properly formatted presentation-format
// domain names. If the response contains invalid names, those records are filtered
// out and an error will be returned alongside the remaining results, if any.
func (r *Resolver) LookupAddr(ctx context.Context, addr string) ([]string, Result, error) {
names, result, err := r.lookupAddr(ctx, addr)
if err != nil {
return nil, result, err
}
filteredNames := make([]string, 0, len(names))
for _, name := range names {
if isDomainName(name) {
filteredNames = append(filteredNames, name)
}
}
if len(names) != len(filteredNames) {
return filteredNames, result, &DNSError{Err: errMalformedDNSRecordsDetail, Name: addr}
}
return filteredNames, result, nil
}
// LookupTLSA calls LookupTLSA on the DefaultResolver.
func LookupTLSA(port int, protocol, host string) ([]TLSA, Result, error) {
return DefaultResolver.LookupTLSA(context.Background(), port, protocol, host)
}
// LookupTLSA looks up a TLSA (TLS association) record for the port (service)
// and protocol (e.g. tcp, udp) at the host.
//
// LookupTLSA looks up DNS name "_<port>._<protocol>.host". Except when port is 0
// and protocol the empty string, then host is directly used to look up the TLSA
// record.
//
// Callers must check the Authentic field of the Result before using a TLSA
// record.
//
// Callers may want to handle DNSError with NotFound set to true (i.e. "nxdomain")
// differently from other errors. DANE support is often optional, with
// protocol-specific fallback behaviour.
//
// LookupTLSA follows CNAME records. For DANE, the secure/insecure DNSSEC
// response must be taken into account when following CNAMEs to determine the
// TLSA base domains. Callers should probably first resolve CNAMEs explicitly
// for their (in)secure status.
func (r *Resolver) LookupTLSA(ctx context.Context, port int, protocol, host string) (records []TLSA, result Result, err error) {
return r.lookupTLSA(ctx, port, protocol, host)
}
// errMalformedDNSRecordsDetail is the DNSError detail which is returned when a Resolver.Lookup...
// method receives DNS records which contain invalid DNS names. This may be returned alongside
// results which have had the malformed records filtered out.
var errMalformedDNSRecordsDetail = "DNS response contained records which contain invalid names"
// dial makes a new connection to the provided server (which must be
// an IP address) with the provided network type, using either r.Dial
// (if both r and r.Dial are non-nil) or else Dialer.DialContext.
func (r *Resolver) dial(ctx context.Context, network, server string) (net.Conn, error) {
// Calling Dial here is scary -- we have to be sure not to
// dial a name that will require a DNS lookup, or Dial will
// call back here to translate it. The DNS config parser has
// already checked that all the cfg.servers are IP
// addresses, which Dial will use without a DNS lookup.
var c net.Conn
var err error
if r != nil && r.Dial != nil {
c, err = r.Dial(ctx, network, server)
} else {
var d net.Dialer
c, err = d.DialContext(ctx, network, server)
}
if err != nil {
return nil, mapErr(err)
}
return c, nil
}
// goLookupSRV returns the SRV records for a target name, built either
// from its component service ("sip"), protocol ("tcp"), and name
// ("example.com."), or from name directly (if service and proto are
// both empty).
//
// In either case, the returned target name ("_sip._tcp.example.com.")
// is also returned on success.
//
// The records are sorted by weight.
func (r *Resolver) goLookupSRV(ctx context.Context, service, proto, name string) (target string, srvs []*net.SRV, result Result, err error) {
if service == "" && proto == "" {
target = name
} else {
target = "_" + service + "._" + proto + "." + name
}
p, server, result, err := r.lookup(ctx, target, dnsmessage.TypeSRV, nil)
if err != nil {
return "", nil, result, err
}
var cname dnsmessage.Name
for {
h, err := p.AnswerHeader()
if err == dnsmessage.ErrSectionDone {
break
}
if err != nil {
return "", nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
if h.Type != dnsmessage.TypeSRV {
if err := p.SkipAnswer(); err != nil {
return "", nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
continue
}
if cname.Length == 0 && h.Name.Length != 0 {
cname = h.Name
}
srv, err := p.SRVResource()
if err != nil {
return "", nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
srvs = append(srvs, &net.SRV{Target: srv.Target.String(), Port: srv.Port, Priority: srv.Priority, Weight: srv.Weight})
}
byPriorityWeight(srvs).sort()
return cname.String(), srvs, result, nil
}
// goLookupMX returns the MX records for name.
func (r *Resolver) goLookupMX(ctx context.Context, name string) ([]*net.MX, Result, error) {
p, server, result, err := r.lookup(ctx, name, dnsmessage.TypeMX, nil)
if err != nil {
return nil, result, err
}
var mxs []*net.MX
for {
h, err := p.AnswerHeader()
if err == dnsmessage.ErrSectionDone {
break
}
if err != nil {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
if h.Type != dnsmessage.TypeMX {
if err := p.SkipAnswer(); err != nil {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
continue
}
mx, err := p.MXResource()
if err != nil {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
mxs = append(mxs, &net.MX{Host: mx.MX.String(), Pref: mx.Pref})
}
byPref(mxs).sort()
return mxs, result, nil
}
// goLookupNS returns the NS records for name.
func (r *Resolver) goLookupNS(ctx context.Context, name string) ([]*net.NS, Result, error) {
p, server, result, err := r.lookup(ctx, name, dnsmessage.TypeNS, nil)
if err != nil {
return nil, result, err
}
var nss []*net.NS
for {
h, err := p.AnswerHeader()
if err == dnsmessage.ErrSectionDone {
break
}
if err != nil {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
if h.Type != dnsmessage.TypeNS {
if err := p.SkipAnswer(); err != nil {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
continue
}
ns, err := p.NSResource()
if err != nil {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
nss = append(nss, &net.NS{Host: ns.NS.String()})
}
return nss, result, nil
}
// goLookupTXT returns the TXT records from name.
func (r *Resolver) goLookupTXT(ctx context.Context, name string) ([]string, Result, error) {
p, server, result, err := r.lookup(ctx, name, dnsmessage.TypeTXT, nil)
if err != nil {
return nil, result, err
}
var txts []string
for {
h, err := p.AnswerHeader()
if err == dnsmessage.ErrSectionDone {
break
}
if err != nil {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
if h.Type != dnsmessage.TypeTXT {
if err := p.SkipAnswer(); err != nil {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
continue
}
txt, err := p.TXTResource()
if err != nil {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
// Multiple strings in one TXT record need to be
// concatenated without separator to be consistent
// with previous Go resolver.
n := 0
for _, s := range txt.TXT {
n += len(s)
}
txtJoin := make([]byte, 0, n)
for _, s := range txt.TXT {
txtJoin = append(txtJoin, s...)
}
if len(txts) == 0 {
txts = make([]string, 0, 1)
}
txts = append(txts, string(txtJoin))
}
return txts, result, nil
}
const typeTLSA = dnsmessage.Type(52)
// goLookupTLSA is the native Go implementation of LookupTLSA.
func (r *Resolver) goLookupTLSA(ctx context.Context, port int, protocol, host string) ([]TLSA, Result, error) {
var name string
if port == 0 && protocol == "" {
name = host
} else {
name = fmt.Sprintf("_%d._%s.%s", port, protocol, host)
}
p, server, result, err := r.lookup(ctx, name, typeTLSA, nil)
if err != nil {
return nil, result, err
}
var l []TLSA
for {
h, err := p.AnswerHeader()
if err == dnsmessage.ErrSectionDone {
break
}
if err != nil {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
if h.Type != typeTLSA {
if err := p.SkipAnswer(); err != nil {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
continue
}
r, err := p.UnknownResource()
if err != nil || len(r.Data) < 3 {
return nil, result, &DNSError{
Err: "cannot unmarshal DNS message",
Name: name,
Server: server,
}
}
record := TLSA{
TLSAUsage(r.Data[0]),
TLSASelector(r.Data[1]),
TLSAMatchType(r.Data[2]),
nil,
}
// We do not verify the contents/size of the data. We don't want to filter out
// values we don't understand. We'll leave it to the callers to see if a record is
// usable. Also because special behaviour may be required if records were found but
// all unusable.
buf := make([]byte, len(r.Data)-3)
copy(buf, r.Data[3:])
record.CertAssoc = buf
l = append(l, record)
}
return l, result, nil
}

105
vendor/github.com/mjl-/adns/lookup_unix.go generated vendored Normal file
View File

@ -0,0 +1,105 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build unix || wasip1
package adns
import (
"context"
"net"
"sync"
"github.com/mjl-/adns/internal/bytealg"
)
var onceReadProtocols sync.Once
// readProtocols loads contents of /etc/protocols into protocols map
// for quick access.
func readProtocols() {
file, err := open("/etc/protocols")
if err != nil {
return
}
defer file.close()
for line, ok := file.readLine(); ok; line, ok = file.readLine() {
// tcp 6 TCP # transmission control protocol
if i := bytealg.IndexByteString(line, '#'); i >= 0 {
line = line[0:i]
}
f := getFields(line)
if len(f) < 2 {
continue
}
if proto, _, ok := dtoi(f[1]); ok {
if _, ok := protocols[f[0]]; !ok {
protocols[f[0]] = proto
}
for _, alias := range f[2:] {
if _, ok := protocols[alias]; !ok {
protocols[alias] = proto
}
}
}
}
}
// lookupProtocol looks up IP protocol name in /etc/protocols and
// returns correspondent protocol number.
func lookupProtocol(_ context.Context, name string) (int, error) {
onceReadProtocols.Do(readProtocols)
return lookupProtocolMap(name)
}
func (r *Resolver) lookupHost(ctx context.Context, host string) (addrs []string, result Result, err error) {
order, conf := systemConf().hostLookupOrder(r, host)
return r.goLookupHostOrder(ctx, host, order, conf)
}
func (r *Resolver) lookupIP(ctx context.Context, network, host string) (addrs []net.IPAddr, result Result, err error) {
if r.preferGo() {
return r.goLookupIP(ctx, network, host)
}
order, conf := systemConf().hostLookupOrder(r, host)
ips, _, result, err := r.goLookupIPCNAMEOrder(ctx, network, host, order, conf)
return ips, result, err
}
func (r *Resolver) lookupPort(ctx context.Context, network, service string) (int, error) {
// Port lookup is not a DNS operation.
// Prefer the cgo resolver if possible.
return goLookupPort(network, service)
}
func (r *Resolver) lookupCNAME(ctx context.Context, name string) (string, Result, error) {
order, conf := systemConf().hostLookupOrder(r, name)
return r.goLookupCNAME(ctx, name, order, conf)
}
func (r *Resolver) lookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, Result, error) {
return r.goLookupSRV(ctx, service, proto, name)
}
func (r *Resolver) lookupMX(ctx context.Context, name string) ([]*net.MX, Result, error) {
return r.goLookupMX(ctx, name)
}
func (r *Resolver) lookupNS(ctx context.Context, name string) ([]*net.NS, Result, error) {
return r.goLookupNS(ctx, name)
}
func (r *Resolver) lookupTXT(ctx context.Context, name string) ([]string, Result, error) {
return r.goLookupTXT(ctx, name)
}
func (r *Resolver) lookupAddr(ctx context.Context, addr string) ([]string, Result, error) {
order, conf := systemConf().addrLookupOrder(r, addr)
return r.goLookupPTR(ctx, addr, order, conf)
}
func (r *Resolver) lookupTLSA(ctx context.Context, port int, protocol, host string) ([]TLSA, Result, error) {
return r.goLookupTLSA(ctx, port, protocol, host)
}

7
vendor/github.com/mjl-/adns/mac.go generated vendored Normal file
View File

@ -0,0 +1,7 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package adns
const hexDigit = "0123456789abcdef"

110
vendor/github.com/mjl-/adns/net.go generated vendored Normal file
View File

@ -0,0 +1,110 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package adns
import (
"context"
"errors"
)
// Various errors contained in OpError.
var (
// For connection setup operations.
errNoSuitableAddress = errors.New("no suitable address found")
// For both read and write operations.
errCanceled = canceledError{}
)
// canceledError lets us return the same error string we have always
// returned, while still being Is context.Canceled.
type canceledError struct{}
func (canceledError) Error() string { return "operation was canceled" }
func (canceledError) Is(err error) bool { return err == context.Canceled }
// mapErr maps from the context errors to the historical internal net
// error values.
func mapErr(err error) error {
switch err {
case context.Canceled:
return errCanceled
case context.DeadlineExceeded:
return errTimeout
default:
return err
}
}
type timeout interface {
Timeout() bool
}
// Various errors contained in DNSError.
var (
errNoSuchHost = errors.New("no such host")
)
// errTimeout exists to return the historical "i/o timeout" string
// for context.DeadlineExceeded. See mapErr.
// It is also used when Dialer.Deadline is exceeded.
// error.Is(errTimeout, context.DeadlineExceeded) returns true.
//
// TODO(iant): We could consider changing this to os.ErrDeadlineExceeded
// in the future, if we make
//
// errors.Is(os.ErrDeadlineExceeded, context.DeadlineExceeded)
//
// return true.
var errTimeout error = &timeoutError{}
type timeoutError struct{}
func (e *timeoutError) Error() string { return "i/o timeout" }
func (e *timeoutError) Timeout() bool { return true }
func (e *timeoutError) Temporary() bool { return true }
func (e *timeoutError) Is(err error) bool {
return err == context.DeadlineExceeded
}
// DNSError represents a DNS lookup error.
type DNSError struct {
Underlying error // Underlying error, could be an ExtendedError.
Err string // description of the error
Name string // name looked for
Server string // server used
IsTimeout bool // if true, timed out; not all timeouts set this
IsTemporary bool // if true, error is temporary; not all errors set this
IsNotFound bool // if true, host could not be found
}
// Unwrap returns the underlying error, which could be an ExtendedError.
func (e *DNSError) Unwrap() error {
return e.Underlying
}
func (e *DNSError) Error() string {
if e == nil {
return "<nil>"
}
s := "lookup " + e.Name
if e.Server != "" {
s += " on " + e.Server
}
s += ": " + e.Err
return s
}
// Timeout reports whether the DNS lookup is known to have timed out.
// This is not always known; a DNS lookup may fail due to a timeout
// and return a DNSError for which Timeout returns false.
func (e *DNSError) Timeout() bool { return e.IsTimeout }
// Temporary reports whether the DNS error is known to be temporary.
// This is not always known; a DNS lookup may fail due to a temporary
// error and return a DNSError for which Temporary returns false.
func (e *DNSError) Temporary() bool { return e.IsTimeout || e.IsTemporary }

250
vendor/github.com/mjl-/adns/nss.go generated vendored Normal file
View File

@ -0,0 +1,250 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package adns
import (
"errors"
"os"
"sync"
"time"
"github.com/mjl-/adns/internal/bytealg"
)
const (
nssConfigPath = "/etc/nsswitch.conf"
)
var nssConfig nsswitchConfig
type nsswitchConfig struct {
initOnce sync.Once // guards init of nsswitchConfig
// ch is used as a semaphore that only allows one lookup at a
// time to recheck nsswitch.conf
ch chan struct{} // guards lastChecked and modTime
lastChecked time.Time // last time nsswitch.conf was checked
mu sync.Mutex // protects nssConf
nssConf *nssConf
}
func getSystemNSS() *nssConf {
nssConfig.tryUpdate()
nssConfig.mu.Lock()
conf := nssConfig.nssConf
nssConfig.mu.Unlock()
return conf
}
// init initializes conf and is only called via conf.initOnce.
func (conf *nsswitchConfig) init() {
conf.nssConf = parseNSSConfFile("/etc/nsswitch.conf")
conf.lastChecked = time.Now()
conf.ch = make(chan struct{}, 1)
}
// tryUpdate tries to update conf.
func (conf *nsswitchConfig) tryUpdate() {
conf.initOnce.Do(conf.init)
// Ensure only one update at a time checks nsswitch.conf
if !conf.tryAcquireSema() {
return
}
defer conf.releaseSema()
now := time.Now()
if conf.lastChecked.After(now.Add(-5 * time.Second)) {
return
}
conf.lastChecked = now
var mtime time.Time
if fi, err := os.Stat(nssConfigPath); err == nil {
mtime = fi.ModTime()
}
if mtime.Equal(conf.nssConf.mtime) {
return
}
nssConf := parseNSSConfFile(nssConfigPath)
conf.mu.Lock()
conf.nssConf = nssConf
conf.mu.Unlock()
}
func (conf *nsswitchConfig) acquireSema() {
conf.ch <- struct{}{}
}
func (conf *nsswitchConfig) tryAcquireSema() bool {
select {
case conf.ch <- struct{}{}:
return true
default:
return false
}
}
func (conf *nsswitchConfig) releaseSema() {
<-conf.ch
}
// nssConf represents the state of the machine's /etc/nsswitch.conf file.
type nssConf struct {
mtime time.Time // time of nsswitch.conf modification
err error // any error encountered opening or parsing the file
sources map[string][]nssSource // keyed by database (e.g. "hosts")
}
type nssSource struct {
source string // e.g. "compat", "files", "mdns4_minimal"
criteria []nssCriterion
}
// standardCriteria reports all specified criteria have the default
// status actions.
func (s nssSource) standardCriteria() bool {
for i, crit := range s.criteria {
if !crit.standardStatusAction(i == len(s.criteria)-1) {
return false
}
}
return true
}
// nssCriterion is the parsed structure of one of the criteria in brackets
// after an NSS source name.
type nssCriterion struct {
negate bool // if "!" was present
status string // e.g. "success", "unavail" (lowercase)
action string // e.g. "return", "continue" (lowercase)
}
// standardStatusAction reports whether c is equivalent to not
// specifying the criterion at all. last is whether this criteria is the
// last in the list.
func (c nssCriterion) standardStatusAction(last bool) bool {
if c.negate {
return false
}
var def string
switch c.status {
case "success":
def = "return"
case "notfound", "unavail", "tryagain":
def = "continue"
default:
// Unknown status
return false
}
if last && c.action == "return" {
return true
}
return c.action == def
}
func parseNSSConfFile(file string) *nssConf {
f, err := open(file)
if err != nil {
return &nssConf{err: err}
}
defer f.close()
mtime, _, err := f.stat()
if err != nil {
return &nssConf{err: err}
}
conf := parseNSSConf(f)
conf.mtime = mtime
return conf
}
func parseNSSConf(f *file) *nssConf {
conf := new(nssConf)
for line, ok := f.readLine(); ok; line, ok = f.readLine() {
line = trimSpace(removeComment(line))
if len(line) == 0 {
continue
}
colon := bytealg.IndexByteString(line, ':')
if colon == -1 {
conf.err = errors.New("no colon on line")
return conf
}
db := trimSpace(line[:colon])
srcs := line[colon+1:]
for {
srcs = trimSpace(srcs)
if len(srcs) == 0 {
break
}
sp := bytealg.IndexByteString(srcs, ' ')
var src string
if sp == -1 {
src = srcs
srcs = "" // done
} else {
src = srcs[:sp]
srcs = trimSpace(srcs[sp+1:])
}
var criteria []nssCriterion
// See if there's a criteria block in brackets.
if len(srcs) > 0 && srcs[0] == '[' {
bclose := bytealg.IndexByteString(srcs, ']')
if bclose == -1 {
conf.err = errors.New("unclosed criterion bracket")
return conf
}
var err error
criteria, err = parseCriteria(srcs[1:bclose])
if err != nil {
conf.err = errors.New("invalid criteria: " + srcs[1:bclose])
return conf
}
srcs = srcs[bclose+1:]
}
if conf.sources == nil {
conf.sources = make(map[string][]nssSource)
}
conf.sources[db] = append(conf.sources[db], nssSource{
source: src,
criteria: criteria,
})
}
}
return conf
}
// parses "foo=bar !foo=bar"
func parseCriteria(x string) (c []nssCriterion, err error) {
err = foreachField(x, func(f string) error {
not := false
if len(f) > 0 && f[0] == '!' {
not = true
f = f[1:]
}
if len(f) < 3 {
return errors.New("criterion too short")
}
eq := bytealg.IndexByteString(f, '=')
if eq == -1 {
return errors.New("criterion lacks equal sign")
}
if hasUpperCase(f) {
lower := []byte(f)
lowerASCIIBytes(lower)
f = string(lower)
}
c = append(c, nssCriterion{
negate: not,
status: f[:eq],
action: f[eq+1:],
})
return nil
})
return
}

267
vendor/github.com/mjl-/adns/parse.go generated vendored Normal file
View File

@ -0,0 +1,267 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Simple file i/o and string manipulation, to avoid
// depending on strconv and bufio and strings.
package adns
import (
"io"
"os"
"time"
"github.com/mjl-/adns/internal/bytealg"
)
type file struct {
file *os.File
data []byte
atEOF bool
}
func (f *file) close() { f.file.Close() }
func (f *file) getLineFromData() (s string, ok bool) {
data := f.data
i := 0
for i = 0; i < len(data); i++ {
if data[i] == '\n' {
s = string(data[0:i])
ok = true
// move data
i++
n := len(data) - i
copy(data[0:], data[i:])
f.data = data[0:n]
return
}
}
if f.atEOF && len(f.data) > 0 {
// EOF, return all we have
s = string(data)
f.data = f.data[0:0]
ok = true
}
return
}
func (f *file) readLine() (s string, ok bool) {
if s, ok = f.getLineFromData(); ok {
return
}
if len(f.data) < cap(f.data) {
ln := len(f.data)
n, err := io.ReadFull(f.file, f.data[ln:cap(f.data)])
if n >= 0 {
f.data = f.data[0 : ln+n]
}
if err == io.EOF || err == io.ErrUnexpectedEOF {
f.atEOF = true
}
}
s, ok = f.getLineFromData()
return
}
func (f *file) stat() (mtime time.Time, size int64, err error) {
st, err := f.file.Stat()
if err != nil {
return time.Time{}, 0, err
}
return st.ModTime(), st.Size(), nil
}
func open(name string) (*file, error) {
fd, err := os.Open(name)
if err != nil {
return nil, err
}
return &file{fd, make([]byte, 0, 64*1024), false}, nil
}
func stat(name string) (mtime time.Time, size int64, err error) {
st, err := os.Stat(name)
if err != nil {
return time.Time{}, 0, err
}
return st.ModTime(), st.Size(), nil
}
// Count occurrences in s of any bytes in t.
func countAnyByte(s string, t string) int {
n := 0
for i := 0; i < len(s); i++ {
if bytealg.IndexByteString(t, s[i]) >= 0 {
n++
}
}
return n
}
// Split s at any bytes in t.
func splitAtBytes(s string, t string) []string {
a := make([]string, 1+countAnyByte(s, t))
n := 0
last := 0
for i := 0; i < len(s); i++ {
if bytealg.IndexByteString(t, s[i]) >= 0 {
if last < i {
a[n] = s[last:i]
n++
}
last = i + 1
}
}
if last < len(s) {
a[n] = s[last:]
n++
}
return a[0:n]
}
func getFields(s string) []string { return splitAtBytes(s, " \r\t\n") }
// Bigger than we need, not too big to worry about overflow
const big = 0xFFFFFF
// Decimal to integer.
// Returns number, characters consumed, success.
func dtoi(s string) (n int, i int, ok bool) {
n = 0
for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
n = n*10 + int(s[i]-'0')
if n >= big {
return big, i, false
}
}
if i == 0 {
return 0, 0, false
}
return n, i, true
}
// Number of occurrences of b in s.
func count(s string, b byte) int {
n := 0
for i := 0; i < len(s); i++ {
if s[i] == b {
n++
}
}
return n
}
// Index of rightmost occurrence of b in s.
func last(s string, b byte) int {
i := len(s)
for i--; i >= 0; i-- {
if s[i] == b {
break
}
}
return i
}
// hasUpperCase tells whether the given string contains at least one upper-case.
func hasUpperCase(s string) bool {
for i := range s {
if 'A' <= s[i] && s[i] <= 'Z' {
return true
}
}
return false
}
// lowerASCIIBytes makes x ASCII lowercase in-place.
func lowerASCIIBytes(x []byte) {
for i, b := range x {
if 'A' <= b && b <= 'Z' {
x[i] += 'a' - 'A'
}
}
}
// lowerASCII returns the ASCII lowercase version of b.
func lowerASCII(b byte) byte {
if 'A' <= b && b <= 'Z' {
return b + ('a' - 'A')
}
return b
}
// trimSpace returns x without any leading or trailing ASCII whitespace.
func trimSpace(x string) string {
for len(x) > 0 && isSpace(x[0]) {
x = x[1:]
}
for len(x) > 0 && isSpace(x[len(x)-1]) {
x = x[:len(x)-1]
}
return x
}
// isSpace reports whether b is an ASCII space character.
func isSpace(b byte) bool {
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
}
// removeComment returns line, removing any '#' byte and any following
// bytes.
func removeComment(line string) string {
if i := bytealg.IndexByteString(line, '#'); i != -1 {
return line[:i]
}
return line
}
// foreachField runs fn on each non-empty run of non-space bytes in x.
// It returns the first non-nil error returned by fn.
func foreachField(x string, fn func(field string) error) error {
x = trimSpace(x)
for len(x) > 0 {
sp := bytealg.IndexByteString(x, ' ')
if sp == -1 {
return fn(x)
}
if field := trimSpace(x[:sp]); len(field) > 0 {
if err := fn(field); err != nil {
return err
}
}
x = trimSpace(x[sp+1:])
}
return nil
}
// stringsHasSuffix is strings.HasSuffix. It reports whether s ends in
// suffix.
func stringsHasSuffix(s, suffix string) bool {
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}
// stringsHasSuffixFold reports whether s ends in suffix,
// ASCII-case-insensitively.
func stringsHasSuffixFold(s, suffix string) bool {
return len(s) >= len(suffix) && stringsEqualFold(s[len(s)-len(suffix):], suffix)
}
// stringsHasPrefix is strings.HasPrefix. It reports whether s begins with prefix.
func stringsHasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}
// stringsEqualFold is strings.EqualFold, ASCII only. It reports whether s and t
// are equal, ASCII-case-insensitively.
func stringsEqualFold(s, t string) bool {
if len(s) != len(t) {
return false
}
for i := 0; i < len(s); i++ {
if lowerASCII(s[i]) != lowerASCII(t[i]) {
return false
}
}
return true
}

62
vendor/github.com/mjl-/adns/port.go generated vendored Normal file
View File

@ -0,0 +1,62 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package adns
// parsePort parses service as a decimal integer and returns the
// corresponding value as port. It is the caller's responsibility to
// parse service as a non-decimal integer when needsLookup is true.
//
// Some system resolvers will return a valid port number when given a number
// over 65536 (see https://golang.org/issues/11715). Alas, the parser
// can't bail early on numbers > 65536. Therefore reasonably large/small
// numbers are parsed in full and rejected if invalid.
func parsePort(service string) (port int, needsLookup bool) {
if service == "" {
// Lock in the legacy behavior that an empty string
// means port 0. See golang.org/issue/13610.
return 0, false
}
const (
max = uint32(1<<32 - 1)
cutoff = uint32(1 << 30)
)
neg := false
if service[0] == '+' {
service = service[1:]
} else if service[0] == '-' {
neg = true
service = service[1:]
}
var n uint32
for _, d := range service {
if '0' <= d && d <= '9' {
d -= '0'
} else {
return 0, true
}
if n >= cutoff {
n = max
break
}
n *= 10
nn := n + uint32(d)
if nn < n || nn > max {
n = max
break
}
n = nn
}
if !neg && n >= cutoff {
port = int(cutoff - 1)
} else if neg && n > cutoff {
port = int(cutoff)
} else {
port = int(n)
}
if neg {
port = -port
}
return port, false
}

58
vendor/github.com/mjl-/adns/port_unix.go generated vendored Normal file
View File

@ -0,0 +1,58 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build unix || (js && wasm) || wasip1
// Read system port mappings from /etc/services
package adns
import (
"sync"
"github.com/mjl-/adns/internal/bytealg"
)
var onceReadServices sync.Once
func readServices() {
file, err := open("/etc/services")
if err != nil {
return
}
defer file.close()
for line, ok := file.readLine(); ok; line, ok = file.readLine() {
// "http 80/tcp www www-http # World Wide Web HTTP"
if i := bytealg.IndexByteString(line, '#'); i >= 0 {
line = line[:i]
}
f := getFields(line)
if len(f) < 2 {
continue
}
portnet := f[1] // "80/tcp"
port, j, ok := dtoi(portnet)
if !ok || port <= 0 || j >= len(portnet) || portnet[j] != '/' {
continue
}
netw := portnet[j+1:] // "tcp"
m, ok1 := services[netw]
if !ok1 {
m = make(map[string]int)
services[netw] = m
}
for i := 0; i < len(f); i++ {
if i != 1 { // f[1] was port/net
m[f[i]] = port
}
}
}
}
// goLookupPort is the native Go implementation of LookupPort.
func goLookupPort(network, service string) (port int, err error) {
onceReadServices.Do(readServices)
return lookupPortMap(network, service)
}

115
vendor/github.com/mjl-/adns/tlsa.go generated vendored Normal file
View File

@ -0,0 +1,115 @@
package adns
import (
"fmt"
)
// TLSAUsage indicates which certificate/public key verification must be done.
type TLSAUsage uint8
const (
// PKIX/WebPKI, certificate must be valid (name, expiry, signed by CA, etc) and
// signed by the trusted-anchor (TA) in this record.
TLSAUsagePKIXTA TLSAUsage = 0
// PKIX/WebPKI, certificate must be valid (name, expiry, signed by CA, etc) and
// match the certificate in the record.
TLSAUsagePKIXEE TLSAUsage = 1
// Certificate must be signed by trusted-anchor referenced in record, with matching
// name, non-expired, etc.
TLSAUsageDANETA TLSAUsage = 2
// Certificate must match the record. No further requirements on name, expiration
// or who signed it.
TLSAUsageDANEEE TLSAUsage = 3
)
// String returns the lower-case acronym of a usage, or "(unknown)" for
// unrecognized values.
func (u TLSAUsage) String() string {
switch u {
case TLSAUsagePKIXTA:
return "pkix-ta"
case TLSAUsagePKIXEE:
return "pkix-ee"
case TLSAUsageDANETA:
return "dane-ta"
case TLSAUsageDANEEE:
return "dane-ee"
}
return "(unknown)"
}
// TLSASelecter indicates the data the "certificate association" field is based on.
type TLSASelector uint8
const (
// DER-encoded x509 certificate.
TLSASelectorCert TLSASelector = 0
// DER-encoded subject public key info (SPKI), so only the public key and its type.
TLSASelectorSPKI TLSASelector = 1
)
// String returns the lower-case acronym of a selector, or "(unknown)" for
// unrecognized values.
func (s TLSASelector) String() string {
switch s {
case TLSASelectorCert:
return "cert"
case TLSASelectorSPKI:
return "spki"
}
return "(unknown)"
}
// TLSAMatchType indicates in which form the data as indicated by the selector
// is stored in the record as certificate association.
type TLSAMatchType uint8
const (
// Full data, e.g. a full DER-encoded SPKI or even certificate.
TLSAMatchTypeFull TLSAMatchType = 0
// SHA2-256-hashed data, either SPKI or certificate.
TLSAMatchTypeSHA256 TLSAMatchType = 1
// SHA2-512-hashed data.
TLSAMatchTypeSHA512 TLSAMatchType = 2
)
// String returns the lower-case acronym of a match type, or "(unknown)" for
// unrecognized values.
func (mt TLSAMatchType) String() string {
switch mt {
case TLSAMatchTypeFull:
return "full"
case TLSAMatchTypeSHA256:
return "sha2-256"
case TLSAMatchTypeSHA512:
return "sha2-512"
}
return "(unknown)"
}
// TLSA represents a TLSA DNS record.
type TLSA struct {
Usage TLSAUsage // Which validations must be performed.
Selector TLSASelector // What needs to be validated (full certificate or only public key).
MatchType TLSAMatchType // In which form the certificate/public key is stored in CertAssoc.
CertAssoc []byte // Certificate association data.
}
// Record returns a TLSA record value for inclusion in DNS. For example:
//
// 3 1 1 133b919c9d65d8b1488157315327334ead8d83372db57465ecabf53ee5748aee
//
// A full record in a zone file may look like this:
//
// _25._tcp.example.com. IN TLSA 3 1 1 133b919c9d65d8b1488157315327334ead8d83372db57465ecabf53ee5748aee
//
// This record is dane-ee (3), spki (1), sha2-256 (1), and the hexadecimal data
// is the sha2-256 hash.
func (r TLSA) Record() string {
return fmt.Sprintf("%d %d %d %x", r.Usage, r.Selector, r.MatchType, r.CertAssoc)
}
// String is like Record but prints both the acronym and code for each field.
func (r TLSA) String() string {
return fmt.Sprintf("%s(%d) %s(%d) %s(%d) %x", r.Usage, r.Usage, r.Selector, r.Selector, r.MatchType, r.MatchType, r.CertAssoc)
}

27
vendor/github.com/mjl-/autocert/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
vendor/github.com/mjl-/autocert/README.txt generated vendored Normal file
View File

@ -0,0 +1 @@
From golang.org/x/crypto/acme/autocert, with Manager.GetPrivateKey added.

1228
vendor/github.com/mjl-/autocert/autocert.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

135
vendor/github.com/mjl-/autocert/cache.go generated vendored Normal file
View File

@ -0,0 +1,135 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package autocert
import (
"context"
"errors"
"os"
"path/filepath"
)
// ErrCacheMiss is returned when a certificate is not found in cache.
var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
// Cache is used by Manager to store and retrieve previously obtained certificates
// and other account data as opaque blobs.
//
// Cache implementations should not rely on the key naming pattern. Keys can
// include any printable ASCII characters, except the following: \/:*?"<>|
type Cache interface {
// Get returns a certificate data for the specified key.
// If there's no such key, Get returns ErrCacheMiss.
Get(ctx context.Context, key string) ([]byte, error)
// Put stores the data in the cache under the specified key.
// Underlying implementations may use any data storage format,
// as long as the reverse operation, Get, results in the original data.
Put(ctx context.Context, key string, data []byte) error
// Delete removes a certificate data from the cache under the specified key.
// If there's no such key in the cache, Delete returns nil.
Delete(ctx context.Context, key string) error
}
// DirCache implements Cache using a directory on the local filesystem.
// If the directory does not exist, it will be created with 0700 permissions.
type DirCache string
// Get reads a certificate data from the specified file name.
func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
name = filepath.Join(string(d), filepath.Clean("/"+name))
var (
data []byte
err error
done = make(chan struct{})
)
go func() {
data, err = os.ReadFile(name)
close(done)
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-done:
}
if os.IsNotExist(err) {
return nil, ErrCacheMiss
}
return data, err
}
// Put writes the certificate data to the specified file name.
// The file will be created with 0600 permissions.
func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
if err := os.MkdirAll(string(d), 0700); err != nil {
return err
}
done := make(chan struct{})
var err error
go func() {
defer close(done)
var tmp string
if tmp, err = d.writeTempFile(name, data); err != nil {
return
}
defer os.Remove(tmp)
select {
case <-ctx.Done():
// Don't overwrite the file if the context was canceled.
default:
newName := filepath.Join(string(d), filepath.Clean("/"+name))
err = os.Rename(tmp, newName)
}
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-done:
}
return err
}
// Delete removes the specified file name.
func (d DirCache) Delete(ctx context.Context, name string) error {
name = filepath.Join(string(d), filepath.Clean("/"+name))
var (
err error
done = make(chan struct{})
)
go func() {
err = os.Remove(name)
close(done)
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-done:
}
if err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
// writeTempFile writes b to a temporary file, closes the file and returns its path.
func (d DirCache) writeTempFile(prefix string, b []byte) (name string, reterr error) {
// TempFile uses 0600 permissions
f, err := os.CreateTemp(string(d), prefix)
if err != nil {
return "", err
}
defer func() {
if reterr != nil {
os.Remove(f.Name())
}
}()
if _, err := f.Write(b); err != nil {
f.Close()
return "", err
}
return f.Name(), f.Close()
}

155
vendor/github.com/mjl-/autocert/listener.go generated vendored Normal file
View File

@ -0,0 +1,155 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package autocert
import (
"crypto/tls"
"log"
"net"
"os"
"path/filepath"
"runtime"
"time"
)
// NewListener returns a net.Listener that listens on the standard TLS
// port (443) on all interfaces and returns *tls.Conn connections with
// LetsEncrypt certificates for the provided domain or domains.
//
// It enables one-line HTTPS servers:
//
// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
//
// NewListener is a convenience function for a common configuration.
// More complex or custom configurations can use the autocert.Manager
// type instead.
//
// Use of this function implies acceptance of the LetsEncrypt Terms of
// Service. If domains is not empty, the provided domains are passed
// to HostWhitelist. If domains is empty, the listener will do
// LetsEncrypt challenges for any requested domain, which is not
// recommended.
//
// Certificates are cached in a "golang-autocert" directory under an
// operating system-specific cache or temp directory. This may not
// be suitable for servers spanning multiple machines.
//
// The returned listener uses a *tls.Config that enables HTTP/2, and
// should only be used with servers that support HTTP/2.
//
// The returned Listener also enables TCP keep-alives on the accepted
// connections. The returned *tls.Conn are returned before their TLS
// handshake has completed.
func NewListener(domains ...string) net.Listener {
m := &Manager{
Prompt: AcceptTOS,
}
if len(domains) > 0 {
m.HostPolicy = HostWhitelist(domains...)
}
dir := cacheDir()
if err := os.MkdirAll(dir, 0700); err != nil {
log.Printf("warning: autocert.NewListener not using a cache: %v", err)
} else {
m.Cache = DirCache(dir)
}
return m.Listener()
}
// Listener listens on the standard TLS port (443) on all interfaces
// and returns a net.Listener returning *tls.Conn connections.
//
// The returned listener uses a *tls.Config that enables HTTP/2, and
// should only be used with servers that support HTTP/2.
//
// The returned Listener also enables TCP keep-alives on the accepted
// connections. The returned *tls.Conn are returned before their TLS
// handshake has completed.
//
// Unlike NewListener, it is the caller's responsibility to initialize
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
func (m *Manager) Listener() net.Listener {
ln := &listener{
conf: m.TLSConfig(),
}
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
return ln
}
type listener struct {
conf *tls.Config
tcpListener net.Listener
tcpListenErr error
}
func (ln *listener) Accept() (net.Conn, error) {
if ln.tcpListenErr != nil {
return nil, ln.tcpListenErr
}
conn, err := ln.tcpListener.Accept()
if err != nil {
return nil, err
}
tcpConn := conn.(*net.TCPConn)
// Because Listener is a convenience function, help out with
// this too. This is not possible for the caller to set once
// we return a *tcp.Conn wrapping an inaccessible net.Conn.
// If callers don't want this, they can do things the manual
// way and tweak as needed. But this is what net/http does
// itself, so copy that. If net/http changes, we can change
// here too.
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(3 * time.Minute)
return tls.Server(tcpConn, ln.conf), nil
}
func (ln *listener) Addr() net.Addr {
if ln.tcpListener != nil {
return ln.tcpListener.Addr()
}
// net.Listen failed. Return something non-nil in case callers
// call Addr before Accept:
return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
}
func (ln *listener) Close() error {
if ln.tcpListenErr != nil {
return ln.tcpListenErr
}
return ln.tcpListener.Close()
}
func homeDir() string {
if runtime.GOOS == "windows" {
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
}
if h := os.Getenv("HOME"); h != "" {
return h
}
return "/"
}
func cacheDir() string {
const base = "golang-autocert"
switch runtime.GOOS {
case "darwin":
return filepath.Join(homeDir(), "Library", "Caches", base)
case "windows":
for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
if v := os.Getenv(ev); v != "" {
return filepath.Join(v, base)
}
}
// Worst case:
return filepath.Join(homeDir(), base)
}
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
return filepath.Join(xdg, base)
}
return filepath.Join(homeDir(), ".cache", base)
}

156
vendor/github.com/mjl-/autocert/renewal.go generated vendored Normal file
View File

@ -0,0 +1,156 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package autocert
import (
"context"
"crypto"
"sync"
"time"
)
// renewJitter is the maximum deviation from Manager.RenewBefore.
const renewJitter = time.Hour
// domainRenewal tracks the state used by the periodic timers
// renewing a single domain's cert.
type domainRenewal struct {
m *Manager
ck certKey
key crypto.Signer
timerMu sync.Mutex
timer *time.Timer
timerClose chan struct{} // if non-nil, renew closes this channel (and nils out the timer fields) instead of running
}
// start starts a cert renewal timer at the time
// defined by the certificate expiration time exp.
//
// If the timer is already started, calling start is a noop.
func (dr *domainRenewal) start(exp time.Time) {
dr.timerMu.Lock()
defer dr.timerMu.Unlock()
if dr.timer != nil {
return
}
dr.timer = time.AfterFunc(dr.next(exp), dr.renew)
}
// stop stops the cert renewal timer and waits for any in-flight calls to renew
// to complete. If the timer is already stopped, calling stop is a noop.
func (dr *domainRenewal) stop() {
dr.timerMu.Lock()
defer dr.timerMu.Unlock()
for {
if dr.timer == nil {
return
}
if dr.timer.Stop() {
dr.timer = nil
return
} else {
// dr.timer fired, and we acquired dr.timerMu before the renew callback did.
// (We know this because otherwise the renew callback would have reset dr.timer!)
timerClose := make(chan struct{})
dr.timerClose = timerClose
dr.timerMu.Unlock()
<-timerClose
dr.timerMu.Lock()
}
}
}
// renew is called periodically by a timer.
// The first renew call is kicked off by dr.start.
func (dr *domainRenewal) renew() {
dr.timerMu.Lock()
defer dr.timerMu.Unlock()
if dr.timerClose != nil {
close(dr.timerClose)
dr.timer, dr.timerClose = nil, nil
return
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
// TODO: rotate dr.key at some point?
next, err := dr.do(ctx)
if err != nil {
next = renewJitter / 2
next += time.Duration(pseudoRand.int63n(int64(next)))
}
testDidRenewLoop(next, err)
dr.timer = time.AfterFunc(next, dr.renew)
}
// updateState locks and replaces the relevant Manager.state item with the given
// state. It additionally updates dr.key with the given state's key.
func (dr *domainRenewal) updateState(state *certState) {
dr.m.stateMu.Lock()
defer dr.m.stateMu.Unlock()
dr.key = state.key
dr.m.state[dr.ck] = state
}
// do is similar to Manager.createCert but it doesn't lock a Manager.state item.
// Instead, it requests a new certificate independently and, upon success,
// replaces dr.m.state item with a new one and updates cache for the given domain.
//
// It may lock and update the Manager.state if the expiration date of the currently
// cached cert is far enough in the future.
//
// The returned value is a time interval after which the renewal should occur again.
func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
// a race is likely unavoidable in a distributed environment
// but we try nonetheless
if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil {
next := dr.next(tlscert.Leaf.NotAfter)
if next > dr.m.renewBefore()+renewJitter {
signer, ok := tlscert.PrivateKey.(crypto.Signer)
if ok {
state := &certState{
key: signer,
cert: tlscert.Certificate,
leaf: tlscert.Leaf,
}
dr.updateState(state)
return next, nil
}
}
}
der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.ck)
if err != nil {
return 0, err
}
state := &certState{
key: dr.key,
cert: der,
leaf: leaf,
}
tlscert, err := state.tlscert()
if err != nil {
return 0, err
}
if err := dr.m.cachePut(ctx, dr.ck, tlscert); err != nil {
return 0, err
}
dr.updateState(state)
return dr.next(leaf.NotAfter), nil
}
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
d := expiry.Sub(dr.m.now()) - dr.m.renewBefore()
// add a bit of randomness to renew deadline
n := pseudoRand.int63n(int64(renewJitter))
d -= time.Duration(n)
if d < 0 {
return 0
}
return d
}
var testDidRenewLoop = func(next time.Duration, err error) {}