Redesigned Client API around net.Dialer (#511)
* Redesigned Client API around net.Dialer * Moved Dialer under Client; reverted msgHash; removed unneeded deprecation * Reverted client_compat.go into client.go and renamed dialer variable * Shortened comment lines * Erroneously removed pointer declaration
This commit is contained in:
parent
032fbabc82
commit
aade52d68e
340
client.go
340
client.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,11 +28,15 @@ type Conn struct {
|
||||||
|
|
||||||
// A Client defines parameters for a DNS client.
|
// A Client defines parameters for a DNS client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP)
|
Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP)
|
||||||
UDPSize uint16 // minimum receive buffer for UDP messages
|
UDPSize uint16 // minimum receive buffer for UDP messages
|
||||||
TLSConfig *tls.Config // TLS connection configuration
|
TLSConfig *tls.Config // TLS connection configuration
|
||||||
Timeout time.Duration // a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout and WriteTimeout when non-zero
|
Dialer *net.Dialer // a net.Dialer used to set local address, timeouts and more
|
||||||
DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds - overridden by Timeout when that value is non-zero
|
// Timeout is a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout,
|
||||||
|
// WriteTimeout when non-zero. Can be overridden with net.Dialer.Timeout (see Client.ExchangeWithDialer and
|
||||||
|
// Client.Dialer) or context.Context.Deadline (see the deprecated ExchangeContext)
|
||||||
|
Timeout time.Duration
|
||||||
|
DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds, or net.Dialer.Timeout if expiring earlier - overridden by Timeout when that value is non-zero
|
||||||
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
|
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
|
||||||
WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
|
WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
|
||||||
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be fully qualified
|
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be fully qualified
|
||||||
|
@ -44,140 +49,11 @@ type Client struct {
|
||||||
// will it fall back to TCP in case of truncation.
|
// will it fall back to TCP in case of truncation.
|
||||||
// See client.Exchange for more information on setting larger buffer sizes.
|
// See client.Exchange for more information on setting larger buffer sizes.
|
||||||
func Exchange(m *Msg, a string) (r *Msg, err error) {
|
func Exchange(m *Msg, a string) (r *Msg, err error) {
|
||||||
var co *Conn
|
client := Client{Net: "udp"}
|
||||||
co, err = DialTimeout("udp", a, dnsTimeout)
|
r, _, err = client.Exchange(m, a)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer co.Close()
|
|
||||||
|
|
||||||
opt := m.IsEdns0()
|
|
||||||
// If EDNS0 is used use that for size.
|
|
||||||
if opt != nil && opt.UDPSize() >= MinMsgSize {
|
|
||||||
co.UDPSize = opt.UDPSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
co.SetWriteDeadline(time.Now().Add(dnsTimeout))
|
|
||||||
if err = co.WriteMsg(m); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
co.SetReadDeadline(time.Now().Add(dnsTimeout))
|
|
||||||
r, err = co.ReadMsg()
|
|
||||||
if err == nil && r.Id != m.Id {
|
|
||||||
err = ErrId
|
|
||||||
}
|
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExchangeContext performs a synchronous UDP query, like Exchange. It
|
|
||||||
// additionally obeys deadlines from the passed Context.
|
|
||||||
func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) {
|
|
||||||
// Combine context deadline with built-in timeout. Context chooses whichever
|
|
||||||
// is sooner.
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(ctx, dnsTimeout)
|
|
||||||
defer cancel()
|
|
||||||
deadline, _ := timeoutCtx.Deadline()
|
|
||||||
|
|
||||||
co := new(Conn)
|
|
||||||
dialer := net.Dialer{}
|
|
||||||
co.Conn, err = dialer.DialContext(timeoutCtx, "udp", a)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer co.Conn.Close()
|
|
||||||
|
|
||||||
opt := m.IsEdns0()
|
|
||||||
// If EDNS0 is used use that for size.
|
|
||||||
if opt != nil && opt.UDPSize() >= MinMsgSize {
|
|
||||||
co.UDPSize = opt.UDPSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
co.SetWriteDeadline(deadline)
|
|
||||||
if err = co.WriteMsg(m); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
co.SetReadDeadline(deadline)
|
|
||||||
r, err = co.ReadMsg()
|
|
||||||
if err == nil && r.Id != m.Id {
|
|
||||||
err = ErrId
|
|
||||||
}
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExchangeConn performs a synchronous query. It sends the message m via the connection
|
|
||||||
// c and waits for a reply. The connection c is not closed by ExchangeConn.
|
|
||||||
// This function is going away, but can easily be mimicked:
|
|
||||||
//
|
|
||||||
// co := &dns.Conn{Conn: c} // c is your net.Conn
|
|
||||||
// co.WriteMsg(m)
|
|
||||||
// in, _ := co.ReadMsg()
|
|
||||||
// co.Close()
|
|
||||||
//
|
|
||||||
func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) {
|
|
||||||
println("dns: this function is deprecated")
|
|
||||||
co := new(Conn)
|
|
||||||
co.Conn = c
|
|
||||||
if err = co.WriteMsg(m); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r, err = co.ReadMsg()
|
|
||||||
if err == nil && r.Id != m.Id {
|
|
||||||
err = ErrId
|
|
||||||
}
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exchange performs a synchronous query. It sends the message m to the address
|
|
||||||
// contained in a and waits for a reply. Basic use pattern with a *dns.Client:
|
|
||||||
//
|
|
||||||
// c := new(dns.Client)
|
|
||||||
// in, rtt, err := c.Exchange(message, "127.0.0.1:53")
|
|
||||||
//
|
|
||||||
// Exchange does not retry a failed query, nor will it fall back to TCP in
|
|
||||||
// case of truncation.
|
|
||||||
// It is up to the caller to create a message that allows for larger responses to be
|
|
||||||
// returned. Specifically this means adding an EDNS0 OPT RR that will advertise a larger
|
|
||||||
// buffer, see SetEdns0. Messages without an OPT RR will fallback to the historic limit
|
|
||||||
// of 512 bytes.
|
|
||||||
func (c *Client) Exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
|
||||||
return c.ExchangeContext(context.Background(), m, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExchangeContext acts like Exchange, but honors the deadline on the provided
|
|
||||||
// context, if present. If there is both a context deadline and a configured
|
|
||||||
// timeout on the client, the earliest of the two takes effect.
|
|
||||||
func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) (
|
|
||||||
r *Msg,
|
|
||||||
rtt time.Duration,
|
|
||||||
err error) {
|
|
||||||
if !c.SingleInflight {
|
|
||||||
return c.exchange(ctx, m, a)
|
|
||||||
}
|
|
||||||
// This adds a bunch of garbage, TODO(miek).
|
|
||||||
t := "nop"
|
|
||||||
if t1, ok := TypeToString[m.Question[0].Qtype]; ok {
|
|
||||||
t = t1
|
|
||||||
}
|
|
||||||
cl := "nop"
|
|
||||||
if cl1, ok := ClassToString[m.Question[0].Qclass]; ok {
|
|
||||||
cl = cl1
|
|
||||||
}
|
|
||||||
r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) {
|
|
||||||
return c.exchange(ctx, m, a)
|
|
||||||
})
|
|
||||||
if r != nil && shared {
|
|
||||||
r = r.Copy()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return r, rtt, err
|
|
||||||
}
|
|
||||||
return r, rtt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) dialTimeout() time.Duration {
|
func (c *Client) dialTimeout() time.Duration {
|
||||||
if c.Timeout != 0 {
|
if c.Timeout != 0 {
|
||||||
return c.Timeout
|
return c.Timeout
|
||||||
|
@ -202,40 +78,87 @@ func (c *Client) writeTimeout() time.Duration {
|
||||||
return dnsTimeout
|
return dnsTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) exchange(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
func (c *Client) Dial(address string) (conn *Conn, err error) {
|
||||||
var co *Conn
|
// create a new dialer with the appropriate timeout
|
||||||
|
var d net.Dialer
|
||||||
|
if c.Dialer == nil {
|
||||||
|
d = net.Dialer{}
|
||||||
|
} else {
|
||||||
|
d = net.Dialer(*c.Dialer)
|
||||||
|
}
|
||||||
|
d.Timeout = c.getTimeoutForRequest(c.writeTimeout())
|
||||||
|
|
||||||
network := "udp"
|
network := "udp"
|
||||||
tls := false
|
useTLS := false
|
||||||
|
|
||||||
switch c.Net {
|
switch c.Net {
|
||||||
case "tcp-tls":
|
case "tcp-tls":
|
||||||
network = "tcp"
|
network = "tcp"
|
||||||
tls = true
|
useTLS = true
|
||||||
case "tcp4-tls":
|
case "tcp4-tls":
|
||||||
network = "tcp4"
|
network = "tcp4"
|
||||||
tls = true
|
useTLS = true
|
||||||
case "tcp6-tls":
|
case "tcp6-tls":
|
||||||
network = "tcp6"
|
network = "tcp6"
|
||||||
tls = true
|
useTLS = true
|
||||||
default:
|
default:
|
||||||
if c.Net != "" {
|
if c.Net != "" {
|
||||||
network = c.Net
|
network = c.Net
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var deadline time.Time
|
conn = new(Conn)
|
||||||
if c.Timeout != 0 {
|
if useTLS {
|
||||||
deadline = time.Now().Add(c.Timeout)
|
conn.Conn, err = tls.DialWithDialer(&d, network, address, c.TLSConfig)
|
||||||
}
|
|
||||||
|
|
||||||
dialDeadline := deadlineOrTimeoutOrCtx(ctx, deadline, c.dialTimeout())
|
|
||||||
dialTimeout := dialDeadline.Sub(time.Now())
|
|
||||||
|
|
||||||
if tls {
|
|
||||||
co, err = DialTimeoutWithTLS(network, a, c.TLSConfig, dialTimeout)
|
|
||||||
} else {
|
} else {
|
||||||
co, err = DialTimeout(network, a, dialTimeout)
|
conn.Conn, err = d.Dial(network, address)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange performs a synchronous query. It sends the message m to the address
|
||||||
|
// contained in a and waits for a reply. Basic use pattern with a *dns.Client:
|
||||||
|
//
|
||||||
|
// c := new(dns.Client)
|
||||||
|
// in, rtt, err := c.Exchange(message, "127.0.0.1:53")
|
||||||
|
//
|
||||||
|
// Exchange does not retry a failed query, nor will it fall back to TCP in
|
||||||
|
// case of truncation.
|
||||||
|
// It is up to the caller to create a message that allows for larger responses to be
|
||||||
|
// returned. Specifically this means adding an EDNS0 OPT RR that will advertise a larger
|
||||||
|
// buffer, see SetEdns0. Messages without an OPT RR will fallback to the historic limit
|
||||||
|
// of 512 bytes
|
||||||
|
// To specify a local address or a timeout, the caller has to set the `Client.Dialer`
|
||||||
|
// attribute appropriately
|
||||||
|
func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) {
|
||||||
|
if !c.SingleInflight {
|
||||||
|
return c.exchange(m, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := "nop"
|
||||||
|
if t1, ok := TypeToString[m.Question[0].Qtype]; ok {
|
||||||
|
t = t1
|
||||||
|
}
|
||||||
|
cl := "nop"
|
||||||
|
if cl1, ok := ClassToString[m.Question[0].Qclass]; ok {
|
||||||
|
cl = cl1
|
||||||
|
}
|
||||||
|
r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) {
|
||||||
|
return c.exchange(m, address)
|
||||||
|
})
|
||||||
|
if r != nil && shared {
|
||||||
|
r = r.Copy()
|
||||||
|
}
|
||||||
|
return r, rtt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
||||||
|
var co *Conn
|
||||||
|
|
||||||
|
co, err = c.Dial(a)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
@ -253,12 +176,13 @@ func (c *Client) exchange(ctx context.Context, m *Msg, a string) (r *Msg, rtt ti
|
||||||
}
|
}
|
||||||
|
|
||||||
co.TsigSecret = c.TsigSecret
|
co.TsigSecret = c.TsigSecret
|
||||||
co.SetWriteDeadline(deadlineOrTimeoutOrCtx(ctx, deadline, c.writeTimeout()))
|
// write with the appropriate write timeout
|
||||||
|
co.SetWriteDeadline(time.Now().Add(c.getTimeoutForRequest(c.writeTimeout())))
|
||||||
if err = co.WriteMsg(m); err != nil {
|
if err = co.WriteMsg(m); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
co.SetReadDeadline(deadlineOrTimeoutOrCtx(ctx, deadline, c.readTimeout()))
|
co.SetReadDeadline(time.Now().Add(c.getTimeoutForRequest(c.readTimeout())))
|
||||||
r, err = co.ReadMsg()
|
r, err = co.ReadMsg()
|
||||||
if err == nil && r.Id != m.Id {
|
if err == nil && r.Id != m.Id {
|
||||||
err = ErrId
|
err = ErrId
|
||||||
|
@ -352,7 +276,7 @@ func tcpMsgLen(t io.Reader) (int, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// As seen with my local router/switch, retursn 1 byte on the above read,
|
// As seen with my local router/switch, returns 1 byte on the above read,
|
||||||
// resulting a a ShortRead. Just write it out (instead of loop) and read the
|
// resulting a a ShortRead. Just write it out (instead of loop) and read the
|
||||||
// other byte.
|
// other byte.
|
||||||
if n == 1 {
|
if n == 1 {
|
||||||
|
@ -467,6 +391,24 @@ func (co *Conn) Write(p []byte) (n int, err error) {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the appropriate timeout for a specific request
|
||||||
|
func (c *Client) getTimeoutForRequest(timeout time.Duration) time.Duration {
|
||||||
|
var requestTimeout time.Duration
|
||||||
|
if c.Timeout != 0 {
|
||||||
|
requestTimeout = c.Timeout
|
||||||
|
} else {
|
||||||
|
requestTimeout = timeout
|
||||||
|
}
|
||||||
|
// net.Dialer.Timeout has priority if smaller than the timeouts computed so
|
||||||
|
// far
|
||||||
|
if c.Dialer != nil && c.Dialer.Timeout != 0 {
|
||||||
|
if c.Dialer.Timeout < requestTimeout {
|
||||||
|
requestTimeout = c.Dialer.Timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requestTimeout
|
||||||
|
}
|
||||||
|
|
||||||
// Dial connects to the address on the named network.
|
// Dial connects to the address on the named network.
|
||||||
func Dial(network, address string) (conn *Conn, err error) {
|
func Dial(network, address string) (conn *Conn, err error) {
|
||||||
conn = new(Conn)
|
conn = new(Conn)
|
||||||
|
@ -477,10 +419,44 @@ func Dial(network, address string) (conn *Conn, err error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExchangeContext performs a synchronous UDP query, like Exchange. It
|
||||||
|
// additionally obeys deadlines from the passed Context.
|
||||||
|
func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) {
|
||||||
|
client := Client{Net: "udp"}
|
||||||
|
r, _, err = client.ExchangeContext(ctx, m, a)
|
||||||
|
// ignorint rtt to leave the original ExchangeContext API unchanged, but
|
||||||
|
// this function will go away
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExchangeConn performs a synchronous query. It sends the message m via the connection
|
||||||
|
// c and waits for a reply. The connection c is not closed by ExchangeConn.
|
||||||
|
// This function is going away, but can easily be mimicked:
|
||||||
|
//
|
||||||
|
// co := &dns.Conn{Conn: c} // c is your net.Conn
|
||||||
|
// co.WriteMsg(m)
|
||||||
|
// in, _ := co.ReadMsg()
|
||||||
|
// co.Close()
|
||||||
|
//
|
||||||
|
func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) {
|
||||||
|
println("dns: ExchangeConn: this function is deprecated")
|
||||||
|
co := new(Conn)
|
||||||
|
co.Conn = c
|
||||||
|
if err = co.WriteMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r, err = co.ReadMsg()
|
||||||
|
if err == nil && r.Id != m.Id {
|
||||||
|
err = ErrId
|
||||||
|
}
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
// DialTimeout acts like Dial but takes a timeout.
|
// DialTimeout acts like Dial but takes a timeout.
|
||||||
func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) {
|
func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) {
|
||||||
conn = new(Conn)
|
|
||||||
conn.Conn, err = net.DialTimeout(network, address, timeout)
|
client := Client{Net: "udp", Dialer: &net.Dialer{Timeout: timeout}}
|
||||||
|
conn, err = client.Dial(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -489,8 +465,12 @@ func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, er
|
||||||
|
|
||||||
// DialWithTLS connects to the address on the named network with TLS.
|
// DialWithTLS connects to the address on the named network with TLS.
|
||||||
func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, err error) {
|
func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, err error) {
|
||||||
conn = new(Conn)
|
if !strings.HasSuffix(network, "-tls") {
|
||||||
conn.Conn, err = tls.Dial(network, address, tlsConfig)
|
network += "-tls"
|
||||||
|
}
|
||||||
|
client := Client{Net: network, TLSConfig: tlsConfig}
|
||||||
|
conn, err = client.Dial(address)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -499,33 +479,29 @@ func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, er
|
||||||
|
|
||||||
// DialTimeoutWithTLS acts like DialWithTLS but takes a timeout.
|
// DialTimeoutWithTLS acts like DialWithTLS but takes a timeout.
|
||||||
func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout time.Duration) (conn *Conn, err error) {
|
func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout time.Duration) (conn *Conn, err error) {
|
||||||
var dialer net.Dialer
|
if !strings.HasSuffix(network, "-tls") {
|
||||||
dialer.Timeout = timeout
|
network += "-tls"
|
||||||
|
}
|
||||||
conn = new(Conn)
|
client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}, TLSConfig: tlsConfig}
|
||||||
conn.Conn, err = tls.DialWithDialer(&dialer, network, address, tlsConfig)
|
conn, err = client.Dial(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// deadlineOrTimeout chooses between the provided deadline and timeout
|
// ExchangeContext acts like Exchange, but honors the deadline on the provided
|
||||||
// by always preferring the deadline so long as it's non-zero (regardless
|
// context, if present. If there is both a context deadline and a configured
|
||||||
// of which is bigger), and returns the equivalent deadline value.
|
// timeout on the client, the earliest of the two takes effect.
|
||||||
func deadlineOrTimeout(deadline time.Time, timeout time.Duration) time.Time {
|
func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
||||||
if deadline.IsZero() {
|
var timeout time.Duration
|
||||||
return time.Now().Add(timeout)
|
if deadline, ok := ctx.Deadline(); !ok {
|
||||||
|
timeout = 0
|
||||||
|
} else {
|
||||||
|
timeout = deadline.Sub(time.Now())
|
||||||
}
|
}
|
||||||
return deadline
|
// not passing the context to the underlying calls, as the API does not support
|
||||||
}
|
// context. For timeouts you should set up Client.Dialer and call Client.Exchange.
|
||||||
|
c.Dialer = &net.Dialer{Timeout: timeout}
|
||||||
// deadlineOrTimeoutOrCtx returns the earliest of: a context deadline, or the
|
return c.Exchange(m, a)
|
||||||
// output of deadlineOrtimeout.
|
|
||||||
func deadlineOrTimeoutOrCtx(ctx context.Context, deadline time.Time, timeout time.Duration) time.Time {
|
|
||||||
result := deadlineOrTimeout(deadline, timeout)
|
|
||||||
if ctxDeadline, ok := ctx.Deadline(); ok && ctxDeadline.Before(result) {
|
|
||||||
result = ctxDeadline
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
168
client_test.go
168
client_test.go
|
@ -11,6 +11,29 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestDialUDP(t *testing.T) {
|
||||||
|
HandleFunc("miek.nl.", HelloServer)
|
||||||
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
|
s, addrstr, err := RunLocalUDPServer("[::1]:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
|
}
|
||||||
|
defer s.Shutdown()
|
||||||
|
|
||||||
|
m := new(Msg)
|
||||||
|
m.SetQuestion("miek.nl.", TypeSOA)
|
||||||
|
|
||||||
|
c := new(Client)
|
||||||
|
conn, err := c.Dial(addrstr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to dial: %v", err)
|
||||||
|
}
|
||||||
|
if conn == nil {
|
||||||
|
t.Fatalf("conn is nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestClientSync(t *testing.T) {
|
func TestClientSync(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", HelloServer)
|
HandleFunc("miek.nl.", HelloServer)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
@ -27,9 +50,12 @@ func TestClientSync(t *testing.T) {
|
||||||
c := new(Client)
|
c := new(Client)
|
||||||
r, _, err := c.Exchange(m, addrstr)
|
r, _, err := c.Exchange(m, addrstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to exchange: %v", err)
|
t.Fatalf("failed to exchange: %v", err)
|
||||||
}
|
}
|
||||||
if r != nil && r.Rcode != RcodeSuccess {
|
if r == nil {
|
||||||
|
t.Fatal("response is nil")
|
||||||
|
}
|
||||||
|
if r.Rcode != RcodeSuccess {
|
||||||
t.Errorf("failed to get an valid answer\n%v", r)
|
t.Errorf("failed to get an valid answer\n%v", r)
|
||||||
}
|
}
|
||||||
// And now with plain Exchange().
|
// And now with plain Exchange().
|
||||||
|
@ -42,7 +68,42 @@ func TestClientSync(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientTLSSync(t *testing.T) {
|
func TestClientLocalAddress(t *testing.T) {
|
||||||
|
HandleFunc("miek.nl.", HelloServerEchoAddrPort)
|
||||||
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
|
s, addrstr, err := RunLocalUDPServer("127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
|
}
|
||||||
|
defer s.Shutdown()
|
||||||
|
|
||||||
|
m := new(Msg)
|
||||||
|
m.SetQuestion("miek.nl.", TypeSOA)
|
||||||
|
|
||||||
|
c := new(Client)
|
||||||
|
laddr := net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345, Zone: ""}
|
||||||
|
c.Dialer = &net.Dialer{LocalAddr: &laddr}
|
||||||
|
r, _, err := c.Exchange(m, addrstr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to exchange: %v", err)
|
||||||
|
}
|
||||||
|
if r != nil && r.Rcode != RcodeSuccess {
|
||||||
|
t.Errorf("failed to get an valid answer\n%v", r)
|
||||||
|
}
|
||||||
|
if len(r.Extra) != 1 {
|
||||||
|
t.Errorf("failed to get additional answers\n%v", r)
|
||||||
|
}
|
||||||
|
txt := r.Extra[0].(*TXT)
|
||||||
|
if txt == nil {
|
||||||
|
t.Errorf("invalid TXT response\n%v", txt)
|
||||||
|
}
|
||||||
|
if len(txt.Txt) != 1 || txt.Txt[0] != "127.0.0.1:12345" {
|
||||||
|
t.Errorf("invalid TXT response\n%v", txt.Txt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientTLSSyncV4(t *testing.T) {
|
||||||
HandleFunc("miek.nl.", HelloServer)
|
HandleFunc("miek.nl.", HelloServer)
|
||||||
defer HandleRemove("miek.nl.")
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
|
@ -65,6 +126,8 @@ func TestClientTLSSync(t *testing.T) {
|
||||||
m.SetQuestion("miek.nl.", TypeSOA)
|
m.SetQuestion("miek.nl.", TypeSOA)
|
||||||
|
|
||||||
c := new(Client)
|
c := new(Client)
|
||||||
|
|
||||||
|
// test tcp-tls
|
||||||
c.Net = "tcp-tls"
|
c.Net = "tcp-tls"
|
||||||
c.TLSConfig = &tls.Config{
|
c.TLSConfig = &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
|
@ -72,9 +135,88 @@ func TestClientTLSSync(t *testing.T) {
|
||||||
|
|
||||||
r, _, err := c.Exchange(m, addrstr)
|
r, _, err := c.Exchange(m, addrstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to exchange: %v", err)
|
t.Fatalf("failed to exchange: %v", err)
|
||||||
}
|
}
|
||||||
if r != nil && r.Rcode != RcodeSuccess {
|
if r == nil {
|
||||||
|
t.Fatal("response is nil")
|
||||||
|
}
|
||||||
|
if r.Rcode != RcodeSuccess {
|
||||||
|
t.Errorf("failed to get an valid answer\n%v", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test tcp4-tls
|
||||||
|
c.Net = "tcp4-tls"
|
||||||
|
c.TLSConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err = c.Exchange(m, addrstr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to exchange: %v", err)
|
||||||
|
}
|
||||||
|
if r == nil {
|
||||||
|
t.Fatal("response is nil")
|
||||||
|
}
|
||||||
|
if r.Rcode != RcodeSuccess {
|
||||||
|
t.Errorf("failed to get an valid answer\n%v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientTLSSyncV6(t *testing.T) {
|
||||||
|
HandleFunc("miek.nl.", HelloServer)
|
||||||
|
defer HandleRemove("miek.nl.")
|
||||||
|
|
||||||
|
cert, err := tls.X509KeyPair(CertPEMBlock, KeyPEMBlock)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to build certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
}
|
||||||
|
|
||||||
|
s, addrstr, err := RunLocalTLSServer("[::1]:0", &config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to run test server: %v", err)
|
||||||
|
}
|
||||||
|
defer s.Shutdown()
|
||||||
|
|
||||||
|
m := new(Msg)
|
||||||
|
m.SetQuestion("miek.nl.", TypeSOA)
|
||||||
|
|
||||||
|
c := new(Client)
|
||||||
|
|
||||||
|
// test tcp-tls
|
||||||
|
c.Net = "tcp-tls"
|
||||||
|
c.TLSConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := c.Exchange(m, addrstr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to exchange: %v", err)
|
||||||
|
}
|
||||||
|
if r == nil {
|
||||||
|
t.Fatal("response is nil")
|
||||||
|
}
|
||||||
|
if r.Rcode != RcodeSuccess {
|
||||||
|
t.Errorf("failed to get an valid answer\n%v", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test tcp6-tls
|
||||||
|
c.Net = "tcp6-tls"
|
||||||
|
c.TLSConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err = c.Exchange(m, addrstr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to exchange: %v", err)
|
||||||
|
}
|
||||||
|
if r == nil {
|
||||||
|
t.Fatal("response is nil")
|
||||||
|
}
|
||||||
|
if r.Rcode != RcodeSuccess {
|
||||||
t.Errorf("failed to get an valid answer\n%v", r)
|
t.Errorf("failed to get an valid answer\n%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,11 +262,11 @@ func TestClientEDNS0(t *testing.T) {
|
||||||
c := new(Client)
|
c := new(Client)
|
||||||
r, _, err := c.Exchange(m, addrstr)
|
r, _, err := c.Exchange(m, addrstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to exchange: %v", err)
|
t.Fatalf("failed to exchange: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r != nil && r.Rcode != RcodeSuccess {
|
if r != nil && r.Rcode != RcodeSuccess {
|
||||||
t.Errorf("failed to get an valid answer\n%v", r)
|
t.Errorf("failed to get a valid answer\n%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,11 +313,14 @@ func TestClientEDNS0Local(t *testing.T) {
|
||||||
c := new(Client)
|
c := new(Client)
|
||||||
r, _, err := c.Exchange(m, addrstr)
|
r, _, err := c.Exchange(m, addrstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to exchange: %s", err)
|
t.Fatalf("failed to exchange: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r != nil && r.Rcode != RcodeSuccess {
|
if r == nil {
|
||||||
t.Error("failed to get a valid answer")
|
t.Fatal("response is nil")
|
||||||
|
}
|
||||||
|
if r.Rcode != RcodeSuccess {
|
||||||
|
t.Fatal("failed to get a valid answer")
|
||||||
t.Logf("%v\n", r)
|
t.Logf("%v\n", r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,6 +658,9 @@ func TestConcurrentExchanges(t *testing.T) {
|
||||||
for i := 0; i < len(r); i++ {
|
for i := 0; i < len(r); i++ {
|
||||||
go func(i int) {
|
go func(i int) {
|
||||||
r[i], _, _ = c.Exchange(m.Copy(), addrstr)
|
r[i], _, _ = c.Exchange(m.Copy(), addrstr)
|
||||||
|
if r[i] == nil {
|
||||||
|
t.Fatalf("response %d is nil", i)
|
||||||
|
}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
|
|
26
doc.go
26
doc.go
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Package dns implements a full featured interface to the Domain Name System.
|
Package dns implements a full featured interface to the Domain Name System.
|
||||||
Server- and client-side programming is supported.
|
Server- and client-side programming is supported.
|
||||||
The package allows complete control over what is send out to the DNS. The package
|
The package allows complete control over what is sent out to the DNS. The package
|
||||||
API follows the less-is-more principle, by presenting a small, clean interface.
|
API follows the less-is-more principle, by presenting a small, clean interface.
|
||||||
|
|
||||||
The package dns supports (asynchronous) querying/replying, incoming/outgoing zone transfers,
|
The package dns supports (asynchronous) querying/replying, incoming/outgoing zone transfers,
|
||||||
|
@ -14,7 +14,7 @@ Basic usage pattern for creating a new resource record:
|
||||||
|
|
||||||
r := new(dns.MX)
|
r := new(dns.MX)
|
||||||
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX,
|
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX,
|
||||||
Class: dns.ClassINET, Ttl: 3600}
|
Class: dns.ClassINET, Ttl: 3600}
|
||||||
r.Preference = 10
|
r.Preference = 10
|
||||||
r.Mx = "mx.miek.nl."
|
r.Mx = "mx.miek.nl."
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ Or even:
|
||||||
mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
|
mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
|
||||||
|
|
||||||
In the DNS messages are exchanged, these messages contain resource
|
In the DNS messages are exchanged, these messages contain resource
|
||||||
records (sets). Use pattern for creating a message:
|
records (sets). Use pattern for creating a message:
|
||||||
|
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetQuestion("miek.nl.", dns.TypeMX)
|
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||||
|
@ -51,7 +51,7 @@ The following is slightly more verbose, but more flexible:
|
||||||
m1.Question = make([]dns.Question, 1)
|
m1.Question = make([]dns.Question, 1)
|
||||||
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
|
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
|
||||||
|
|
||||||
After creating a message it can be send.
|
After creating a message it can be sent.
|
||||||
Basic use pattern for synchronous querying the DNS at a
|
Basic use pattern for synchronous querying the DNS at a
|
||||||
server configured on 127.0.0.1 and port 53:
|
server configured on 127.0.0.1 and port 53:
|
||||||
|
|
||||||
|
@ -63,7 +63,23 @@ class) is as easy as setting:
|
||||||
|
|
||||||
c.SingleInflight = true
|
c.SingleInflight = true
|
||||||
|
|
||||||
If these "advanced" features are not needed, a simple UDP query can be send,
|
More advanced options are availabe using a net.Dialer and the corresponding API.
|
||||||
|
For example it is possible to set a timeout, or to specify a source IP address
|
||||||
|
and port to use for the connection:
|
||||||
|
|
||||||
|
c := new(dns.Client)
|
||||||
|
laddr := net.UDPAddr{
|
||||||
|
IP: net.ParseIP("[::1]"),
|
||||||
|
Port: 12345,
|
||||||
|
Zone: "",
|
||||||
|
}
|
||||||
|
d := net.Dialer{
|
||||||
|
Timeout: 200 * time.Millisecond,
|
||||||
|
LocalAddr: &laddr,
|
||||||
|
}
|
||||||
|
in, rtt, err := c.ExchangeWithDialer(&d, m1, "8.8.8.8:53")
|
||||||
|
|
||||||
|
If these "advanced" features are not needed, a simple UDP query can be sent,
|
||||||
with:
|
with:
|
||||||
|
|
||||||
in, err := dns.Exchange(m1, "127.0.0.1:53")
|
in, err := dns.Exchange(m1, "127.0.0.1:53")
|
||||||
|
|
|
@ -30,6 +30,16 @@ func HelloServerBadID(w ResponseWriter, req *Msg) {
|
||||||
w.WriteMsg(m)
|
w.WriteMsg(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HelloServerEchoAddrPort(w ResponseWriter, req *Msg) {
|
||||||
|
m := new(Msg)
|
||||||
|
m.SetReply(req)
|
||||||
|
|
||||||
|
remoteAddr := w.RemoteAddr().String()
|
||||||
|
m.Extra = make([]RR, 1)
|
||||||
|
m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{remoteAddr}}
|
||||||
|
w.WriteMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
func AnotherHelloServer(w ResponseWriter, req *Msg) {
|
func AnotherHelloServer(w ResponseWriter, req *Msg) {
|
||||||
m := new(Msg)
|
m := new(Msg)
|
||||||
m.SetReply(req)
|
m.SetReply(req)
|
||||||
|
|
Loading…
Reference in New Issue