diff --git a/TODO.md b/TODO.md index 64f852f..f2d8f74 100644 --- a/TODO.md +++ b/TODO.md @@ -6,11 +6,11 @@ - [x] Add a resolve method - [x] Make it separate from Hub - [x] Parse output into separate fields -- [ ] Test IPv6/IPv4 only options +- [x] Test IPv6/IPv4 only options - [x] Add DOH support -- [ ] Add DOT support -- [ ] Add DNS protocol on TCP mode support. - - [ ] Change lookup method. +- [x] Add DOT support +- [x] Add DNS protocol on TCP mode support. + - [x] Change lookup method. - [x] Major records supported - [x] Support multiple resolvers - [x] Take multiple transport options and initialise resolvers accordingly. @@ -37,7 +37,7 @@ - [x] Add client transport options - [x] Fix an issue while loading free form args, where the same records are being added twice - [x] Remove urfave/cli in favour of `pflag + koanf` -- [ ] Flags - Remove uneeded ones +- [x] Flags - Remove uneeded ones ## Refactors - [ ] Don't abuse Hub as global. Refactor methods to be independent of hub. diff --git a/cmd/cli.go b/cmd/cli.go index 8cb2979..202b803 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "github.com/knadh/koanf" @@ -39,6 +38,8 @@ func main() { f.Int("timeout", 5, "Sets the timeout for a query to T seconds. The default timeout is 5 seconds.") f.Bool("search", false, "Use the search list provided in resolv.conf. It sets the `ndots` parameter as well unless overriden by `ndots` flag.") f.Int("ndots", 1, "Specify the ndots paramter. Default value is taken from resolv.conf and fallbacks to 1 if ndots statement is missing in resolv.conf") + f.BoolP("ipv4", "4", false, "Use IPv4 only") + f.BoolP("ipv6", "6", false, "Use IPv6 only") // Output Options f.BoolP("json", "J", false, "Set the output format as JSON") @@ -82,7 +83,6 @@ func main() { hub.Logger.Exit(2) } if ns.Address != "" && ns.Type != "" { - fmt.Println("appending", ns.Address, ns.Type) hub.Nameservers = append(hub.Nameservers, ns) } } diff --git a/cmd/resolver.go b/cmd/resolver.go index 53b618f..df0bca5 100644 --- a/cmd/resolver.go +++ b/cmd/resolver.go @@ -19,6 +19,7 @@ const ( DefaultTLSPort = "853" // DefaultUDPPort specifies the default port for a DNS server connecting over UDP DefaultUDPPort = "53" + DefaultTCPPort = "53" UDPResolver = "udp" DOHResolver = "doh" TCPResolver = "tcp" @@ -40,12 +41,28 @@ func (hub *Hub) initResolver() error { } hub.Resolver = append(hub.Resolver, rslvr) } - if ns.Type == TCPResolver { - hub.Logger.Debug("initiating TCP resolver") - rslvr, err := resolvers.NewTCPResolver(ns.Address, resolvers.TCPResolverOpts{ + if ns.Type == DOTResolver { + hub.Logger.Debug("initiating DOT resolver") + rslvr, err := resolvers.NewClassicResolver(ns.Address, resolvers.ClassicResolverOpts{ IPv4Only: hub.QueryFlags.UseIPv4, IPv6Only: hub.QueryFlags.UseIPv6, Timeout: hub.QueryFlags.Timeout * time.Second, + UseTLS: true, + UseTCP: true, + }) + if err != nil { + return err + } + hub.Resolver = append(hub.Resolver, rslvr) + } + if ns.Type == TCPResolver { + hub.Logger.Debug("initiating TCP resolver") + rslvr, err := resolvers.NewClassicResolver(ns.Address, resolvers.ClassicResolverOpts{ + IPv4Only: hub.QueryFlags.UseIPv4, + IPv6Only: hub.QueryFlags.UseIPv6, + Timeout: hub.QueryFlags.Timeout * time.Second, + UseTLS: false, + UseTCP: true, }) if err != nil { return err @@ -54,10 +71,12 @@ func (hub *Hub) initResolver() error { } if ns.Type == UDPResolver { hub.Logger.Debug("initiating UDP resolver") - rslvr, err := resolvers.NewUDPResolver(ns.Address, resolvers.UDPResolverOpts{ + rslvr, err := resolvers.NewClassicResolver(ns.Address, resolvers.ClassicResolverOpts{ IPv4Only: hub.QueryFlags.UseIPv4, IPv6Only: hub.QueryFlags.UseIPv6, Timeout: hub.QueryFlags.Timeout * time.Second, + UseTLS: false, + UseTCP: false, }) if err != nil { return err @@ -113,13 +132,21 @@ func initNameserver(n string) (Nameserver, error) { ns.Type = DOHResolver ns.Address = u.String() } + if u.Scheme == "tls" { + ns.Type = DOTResolver + if u.Port() == "" { + ns.Address = net.JoinHostPort(u.Hostname(), DefaultTLSPort) + } else { + ns.Address = net.JoinHostPort(u.Hostname(), u.Port()) + } + } if u.Scheme == "tcp" { ns.Type = TCPResolver - if i := net.ParseIP(n); i != nil { - // if no port specified in nameserver, append defaults. - n = net.JoinHostPort(n, DefaultTLSPort) + if u.Port() == "" { + ns.Address = net.JoinHostPort(u.Hostname(), DefaultTCPPort) + } else { + ns.Address = net.JoinHostPort(u.Hostname(), u.Port()) } - ns.Address = u.String() } if u.Scheme == "udp" { ns.Type = UDPResolver diff --git a/pkg/resolvers/udp.go b/pkg/resolvers/classic.go similarity index 55% rename from pkg/resolvers/udp.go rename to pkg/resolvers/classic.go index c1240f8..982949a 100644 --- a/pkg/resolvers/udp.go +++ b/pkg/resolvers/classic.go @@ -6,33 +6,47 @@ import ( "github.com/miekg/dns" ) -// UDPResolver represents the config options for setting up a Resolver. -type UDPResolver struct { +// ClassicResolver represents the config options for setting up a Resolver. +type ClassicResolver struct { client *dns.Client server string } -// UDPResolverOpts holds options for setting up a Classic resolver. -type UDPResolverOpts struct { +// ClassicResolverOpts holds options for setting up a Classic resolver. +type ClassicResolverOpts struct { IPv4Only bool IPv6Only bool Timeout time.Duration + UseTLS bool + UseTCP bool } -// NewUDPResolver accepts a list of nameservers and configures a DNS resolver. -func NewUDPResolver(server string, opts UDPResolverOpts) (Resolver, error) { +// NewClassicResolver accepts a list of nameservers and configures a DNS resolver. +func NewClassicResolver(server string, opts ClassicResolverOpts) (Resolver, error) { + net := "udp" client := &dns.Client{ Timeout: opts.Timeout, + Net: "udp", + } + + if opts.UseTCP { + net = "tcp" } - client.Net = "udp" if opts.IPv4Only { - client.Net = "udp4" + net = net + "4" } if opts.IPv6Only { - client.Net = "udp6" + net = net + "6" } - return &UDPResolver{ + + if opts.UseTLS { + net = net + "-tls" + } + + client.Net = net + + return &ClassicResolver{ client: client, server: server, }, nil @@ -41,7 +55,7 @@ func NewUDPResolver(server string, opts UDPResolverOpts) (Resolver, error) { // Lookup prepare a list of DNS messages to be sent to the server. // It's possible to send multiple question in one message // but some nameservers are not able to -func (r *UDPResolver) Lookup(questions []dns.Question) ([]Response, error) { +func (r *ClassicResolver) Lookup(questions []dns.Question) ([]Response, error) { var ( messages = prepareMessages(questions) responses []Response diff --git a/pkg/resolvers/tcp.go b/pkg/resolvers/tcp.go deleted file mode 100644 index 0a321c1..0000000 --- a/pkg/resolvers/tcp.go +++ /dev/null @@ -1,64 +0,0 @@ -package resolvers - -import ( - "time" - - "github.com/miekg/dns" -) - -// TCPResolver represents the config options for setting up a Resolver. -type TCPResolver struct { - client *dns.Client - server string -} - -// TCPResolverOpts represents the config options for setting up a TCPResolver. -type TCPResolverOpts struct { - IPv4Only bool - IPv6Only bool - Timeout time.Duration -} - -// NewTCPResolver accepts a list of nameservers and configures a DNS resolver. -func NewTCPResolver(server string, opts TCPResolverOpts) (Resolver, error) { - client := &dns.Client{ - Timeout: opts.Timeout, - } - - client.Net = "tcp" - if opts.IPv4Only { - client.Net = "tcp4" - } - if opts.IPv6Only { - client.Net = "tcp6" - } - return &TCPResolver{ - client: client, - server: server, - }, nil -} - -// Lookup prepare a list of DNS messages to be sent to the server. -// It's possible to send multiple question in one message -// but some nameservers are not able to -func (r *TCPResolver) Lookup(questions []dns.Question) ([]Response, error) { - var ( - messages = prepareMessages(questions) - responses []Response - ) - - for _, msg := range messages { - in, rtt, err := r.client.Exchange(&msg, r.server) - if err != nil { - return nil, err - } - msg.Answer = in.Answer - rsp := Response{ - Message: msg, - RTT: rtt, - Nameserver: r.server, - } - responses = append(responses, rsp) - } - return responses, nil -}