b2b1a4b2f1
Part of https://github.com/ooni/probe-cli/pull/506. In parallel with tutorials, we also need to make sure we have good documentation.
129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
package netxlite
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
|
|
"github.com/miekg/dns"
|
|
"github.com/ooni/probe-cli/v3/internal/atomicx"
|
|
)
|
|
|
|
// SerialResolver uses a transport and sends performs a LookupHost
|
|
// operation in a serial fashion (query for A first, wait for response,
|
|
// then query for AAAA, and wait for response), hence its name.
|
|
//
|
|
// You should probably use NewSerialResolver to create a new instance.
|
|
type SerialResolver struct {
|
|
// Encoder is the MANDATORY encoder to use.
|
|
Encoder DNSEncoder
|
|
|
|
// Decoder is the MANDATORY decoder to use.
|
|
Decoder DNSDecoder
|
|
|
|
// NumTimeouts is MANDATORY and counts the number of timeouts.
|
|
NumTimeouts *atomicx.Int64
|
|
|
|
// Txp is the underlying DNS transport.
|
|
Txp DNSTransport
|
|
}
|
|
|
|
// NewSerialResolver creates a new SerialResolver instance.
|
|
func NewSerialResolver(t DNSTransport) *SerialResolver {
|
|
return &SerialResolver{
|
|
Encoder: &DNSEncoderMiekg{},
|
|
Decoder: &DNSDecoderMiekg{},
|
|
NumTimeouts: &atomicx.Int64{},
|
|
Txp: t,
|
|
}
|
|
}
|
|
|
|
// Transport returns the transport being used.
|
|
func (r *SerialResolver) Transport() DNSTransport {
|
|
return r.Txp
|
|
}
|
|
|
|
// Network returns the "network" of the underlying transport.
|
|
func (r *SerialResolver) Network() string {
|
|
return r.Txp.Network()
|
|
}
|
|
|
|
// Address returns the "address" of the underlying transport.
|
|
func (r *SerialResolver) Address() string {
|
|
return r.Txp.Address()
|
|
}
|
|
|
|
// CloseIdleConnections closes idle connections, if any.
|
|
func (r *SerialResolver) CloseIdleConnections() {
|
|
r.Txp.CloseIdleConnections()
|
|
}
|
|
|
|
// LookupHost performs an A lookup followed by an AAAA lookup for hostname.
|
|
func (r *SerialResolver) LookupHost(ctx context.Context, hostname string) ([]string, error) {
|
|
var addrs []string
|
|
addrsA, errA := r.lookupHostWithRetry(ctx, hostname, dns.TypeA)
|
|
addrsAAAA, errAAAA := r.lookupHostWithRetry(ctx, hostname, dns.TypeAAAA)
|
|
if errA != nil && errAAAA != nil {
|
|
return nil, errA
|
|
}
|
|
addrs = append(addrs, addrsA...)
|
|
addrs = append(addrs, addrsAAAA...)
|
|
return addrs, nil
|
|
}
|
|
|
|
// LookupHTTPS implements Resolver.LookupHTTPS.
|
|
func (r *SerialResolver) LookupHTTPS(
|
|
ctx context.Context, hostname string) (*HTTPSSvc, error) {
|
|
querydata, err := r.Encoder.Encode(
|
|
hostname, dns.TypeHTTPS, r.Txp.RequiresPadding())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
replydata, err := r.Txp.RoundTrip(ctx, querydata)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.Decoder.DecodeHTTPS(replydata)
|
|
}
|
|
|
|
func (r *SerialResolver) lookupHostWithRetry(
|
|
ctx context.Context, hostname string, qtype uint16) ([]string, error) {
|
|
var errorslist []error
|
|
for i := 0; i < 3; i++ {
|
|
replies, err := r.lookupHostWithoutRetry(ctx, hostname, qtype)
|
|
if err == nil {
|
|
return replies, nil
|
|
}
|
|
errorslist = append(errorslist, err)
|
|
var operr *net.OpError
|
|
if !errors.As(err, &operr) || !operr.Timeout() {
|
|
// The first error is the one that is most likely to be caused
|
|
// by the network. Subsequent errors are more likely to be caused
|
|
// by context deadlines. So, the first error is attached to an
|
|
// operation, while subsequent errors may possibly not be. If
|
|
// so, the resulting failing operation is not correct.
|
|
break
|
|
}
|
|
r.NumTimeouts.Add(1)
|
|
}
|
|
// bugfix: we MUST return one of the errors otherwise we confuse the
|
|
// mechanism in errwrap that classifies the root cause operation, since
|
|
// it would not be able to find a child with a major operation error
|
|
return nil, errorslist[0]
|
|
}
|
|
|
|
// lookupHostWithoutRetry issues a lookup host query for the specified
|
|
// qtype (dns.A or dns.AAAA) without retrying on failure.
|
|
func (r *SerialResolver) lookupHostWithoutRetry(
|
|
ctx context.Context, hostname string, qtype uint16) ([]string, error) {
|
|
querydata, err := r.Encoder.Encode(hostname, qtype, r.Txp.RequiresPadding())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
replydata, err := r.Txp.RoundTrip(ctx, querydata)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.Decoder.DecodeLookupHost(qtype, replydata)
|
|
}
|