Skip to content

Commit

Permalink
Adding a WIP version of dnsping with some basic functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
farrokhi committed Mar 9, 2023
1 parent 9d7dbff commit 140ec53
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 0 deletions.
286 changes: 286 additions & 0 deletions dnsping/dnsping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
//
// Copyright (c) 2016-2023, Babak Farrokhi
// 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.
//
// 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 HOLDER 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.
//

package main

import (
"fmt"
"github.com/pborman/getopt/v2"
"net"
"os"
"strconv"
"strings"
"time"

"github.com/farrokhi/dns"
)

type response struct {
msg *dns.Msg
rtt time.Duration
}

func makeServerAddr(host string, port int) string {

var nameserver string

if isIP := net.ParseIP(host); isIP != nil {
nameserver = net.JoinHostPort(host, strconv.Itoa(port))
} else {
nameserver = net.JoinHostPort(dns.Fqdn(host), strconv.Itoa(port))
}

return nameserver
}

func flagsToText(msg *dns.Msg) (flags string) {

// QR = 0x8000 (Query Response)
// AA = 0x0400 (Authoritative Answer)
// TC = 0x0200 (Truncated Response)
// RD = 0x0100 (Recursion Desired)
// RA = 0x0080 (Recursion Available)
// AD = 0x0020 (Authentic Data)
// CD = 0x0010 (Checking Disabled)

if msg.Response {
flags += " QR"
}
if msg.Authoritative {
flags += " AA"
}
if msg.Truncated {
flags += " TC"
}
if msg.RecursionDesired {
flags += " RD"
}
if msg.RecursionAvailable {
flags += " RA"
}
if msg.Zero { // well, this should not happen
flags += " Z"
}
if msg.AuthenticatedData {
flags += " AD"
}
if msg.CheckingDisabled {
flags += " CD"
}

return strings.TrimSpace(flags)
}

func printUsage() {
fmt.Println("dnsping version 2.1.0")
fmt.Println("usage: dnsping.py [-46DeFhqTvX] [-i interval] [-s server] [-p port] [-P port] [-S address] [-c count] [-t type] [-w wait] hostname")
fmt.Println("")
fmt.Println("-h --help Show this help")
fmt.Println("-q --quiet Quiet output. Only header and statistics are displayed.")
fmt.Println("-v --verbose Print actual dns response")
fmt.Println("-s --server DNS server to use (default: first entry from /etc/resolv.conf)")
fmt.Println("-p --port DNS server port number (default: 53 for TCP/UDP, 853 for TLS and QUIC)")
fmt.Println("-T --tcp Use TCP as transport protocol")
fmt.Println("-X --tls Use TLS as transport protocol")
fmt.Println("-H --doh Use HTTPS as transport protocol (DoH)")
fmt.Println("-Q --quic Use QUIC as transport protocol (DoQ)")
fmt.Println("-4 --ipv4 Use IPv4 as default network protocol")
fmt.Println("-6 --ipv6 Use IPv6 as default network protocol")
fmt.Println("-P --srcport Query source port number (default: 0)")
fmt.Println("-S --srcip Query source IP address (default: default interface address)")
fmt.Println("-c --count Number of requests to send (default: 10, 0 for infinity)")
fmt.Println("-r --norecurse Enforce non-recursive query by clearing RD (recursion desired) bit")
fmt.Println("-m --cache-miss Force cache miss measurement by prepending a random hostname")
fmt.Println("-w --wait Maximum wait time for a reply (default: 2 seconds)")
fmt.Println("-i --interval Time between each request (default: 1 seconds)")
fmt.Println("-t --type DNS request record type (default: A)")
fmt.Println("-e --edns Disable EDNS0 (default: Enabled)")
fmt.Println("-D --dnssec Enable 'DNSSEC desired' flag in requests. Implies EDNS.")
fmt.Println("-F --flags Display response flags")
fmt.Println("")
}

func optOverride[T interface{}](opt string, DV *T, V T) {
if getopt.IsSet(opt) {
*DV = V
}
}

func main() {
var err error
var exists bool
var rsp response

// defaults - to be overridden by CLI options
var (
count uint = 10
interval float64 = 1.0 // seconds
timeout int64 = 2 //seconds
qname string = "wikipedia.org"
server string = "9.9.9.9"
dstport int = 53
proto string = "udp"
qtype string = "A"
noRecurse bool = false
want_dnssec bool = false
use_edns bool = false
show_flags bool = false
//use_TCP bool = false
)

var (
optHelp = getopt.BoolLong("help", 'h', "Show this help")
optQuiet = getopt.BoolLong("quiet", 'q', "Quiet output. Only header and statistics are displayed.")
//optVerbose = getopt.BoolLong("verbose", 'v', "Print actual dns response")
optServer = getopt.StringLong("server", 's', "", "DNS server to use", "server")
optDstPort = getopt.IntLong("port", 'p', dstport, "DNS server port number (default: 53 for TCP/UDP, 853 for TLS and QUIC)", "port")
optTCP = getopt.BoolLong("tcp", 'T', "Use TCP as transport protocol")
//optTLS = getopt.BoolLong("tls", 'X', "Use TLS as transport protocol")
//optDoH = getopt.BoolLong("doh", 'H', "Use HTTPS as transport protocol (DoH)")
//optQUIC = getopt.BoolLong("quic", 'Q', "Use QUIC as transport protocol (DoQ)")
//optIPv4 = getopt.BoolLong("ipv4", '4', "Use IPv4 as default network protocol")
//optIPv6 = getopt.BoolLong("ipv6", '6', "Use IPv6 as default network protocol")
//optSrcPort = getopt.Int16Long("srcport", 'P', 0, "Query source port number (default: 0)")
//optSrcIP = getopt.StringLong("srcip", 'S', "", "Query source IP address (default: default interface address)")
optCount = getopt.UintLong("count", 'c', count, "Number of requests to send (default: 10, 0 for infinity)", "count")
optNoRecurse = getopt.BoolLong("norecurse", 'r', "Enforce non-recursive query by clearing RD (recursion desired) bit")
//optCacheMiss = getopt.BoolLong("cache-miss", 'm', "Force cache miss measurement by prepending a random hostname")
optWaitTime = getopt.Int64Long("wait", 'w', timeout, "Maximum wait time for a reply (in seconds)", "timeout")
//optInterval = getopt.Int64Long("interval", 'i', interval, "Time between each request (in seconds)", "interval")
optType = getopt.StringLong("type", 't', qtype, "DNS request record type", "type")
optEDNS = getopt.BoolLong("edns", 'e', "Enable EDNS0")
optDNSSEC = getopt.BoolLong("dnssec", 'D', "Enable 'DNSSEC desired' (DO flag) in requests. Implies EDNS.")
optFlags = getopt.BoolLong("flags", 'F', "Display response flags")
)

//getopt.SetUsage(printUsage) // use our own help message to keep proper options order

getopt.FlagLong(&interval, "interval", 'i', "Time between each request (in seconds)", "interval")

getopt.Parse()

if getopt.NArgs() < 1 {
fmt.Println("dnsping: missing hostname")
getopt.PrintUsage(os.Stderr)
//printUsage()
os.Exit(1)
}

qname = getopt.Arg(0)

// I don't rely on getopt to keep/set default values, because I need
// more flexibility and need to manipulate options
// e.g. I cannot set default bool to true in getopt.BoolLong()

optOverride("server", &server, *optServer)
optOverride("count", &count, *optCount)
optOverride("type", &qtype, *optType)
optOverride("norecurse", &noRecurse, *optNoRecurse)
optOverride("wait", &timeout, *optWaitTime)
optOverride("edns", &use_edns, *optEDNS)
optOverride("flags", &show_flags, *optFlags)
optOverride("port", &dstport, *optDstPort)
optOverride("dnssec", &want_dnssec, *optDNSSEC)
//optOverride("interval", &interval, *optInterval)

if want_dnssec { // EDNS0 is required to set DO bit
use_edns = true
}

if *optTCP {
//has_TCP = true
proto = "tcp"
}

if *optHelp {
printUsage()
os.Exit(0)
}

// setup client
c := new(dns.Client)
c.Net = proto
c.DialTimeout = time.Duration(timeout) * time.Second
c.ReadTimeout = time.Duration(timeout) * time.Second
c.WriteTimeout = time.Duration(timeout) * time.Second

// setup server
nameserver := makeServerAddr(server, dstport)

// Setup Request
m := new(dns.Msg)
m.MsgHdr = dns.MsgHdr{}
m.Question = make([]dns.Question, 1)
m.Opcode = dns.OpcodeQuery
m.RecursionDesired = !noRecurse
if use_edns {
m.SetEdns0(8192, want_dnssec)
}

// how to set local address
//
// if *laddr != "" {
// c.Dialer = &net.Dialer{Timeout: c.DialTimeout}
// ip := net.ParseIP(*laddr)
// if *tcp {
// c.Dialer.LocalAddr = &net.TCPAddr{IP: ip}
// } else {
// c.Dialer.LocalAddr = &net.UDPAddr{IP: ip}
// }
// }

m.Question[0].Name = dns.Fqdn(qname)
m.Question[0].Qclass = dns.ClassINET
m.Question[0].Qtype, exists = dns.StringToType[qtype]
if !exists {
fmt.Printf("Error: Invalid record type: %s\n", qtype)
os.Exit(1)
}

fmt.Printf("dnsping DNS: %v, hostname: %v, proto: %v, type: %v, flags: [%s]\n", nameserver, qname, proto, *optType, flagsToText(m))

//s := m.Question[0].
for i := uint(0); i < count; i++ {
m.Id = dns.Id() // Need new ID for each request
rsp.msg, rsp.rtt, err = c.Exchange(m, nameserver)
switch {
case err != nil:
fmt.Println(err)
case rsp.msg != nil && !*optQuiet:
fmt.Printf("%d bytes from %s: seq=%-3d time=%-7.3f ms", rsp.msg.PktLen, server, i+1, rsp.rtt.Seconds()*1000)
if show_flags {
fmt.Printf(" [%s] %s", flagsToText(rsp.msg), dns.RcodeToString[rsp.msg.Rcode])
}
fmt.Printf("\n")
default:
panic("empty result")
}

time.Sleep((time.Duration(interval) * time.Second) - rsp.rtt) // return immediately when duration is negative
}
fmt.Printf("\n--- %s dnsping statistics ---\n", server)

}
12 changes: 12 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module dnsdiag

go 1.20

require (
github.com/farrokhi/dns v0.0.0-20230307165215-fdc21600e116 // indirect
github.com/pborman/getopt/v2 v2.1.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/tools v0.3.0 // indirect
)
41 changes: 41 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
github.com/farrokhi/dns v0.0.0-20230307165215-fdc21600e116 h1:/v+AtzxSl2fKM6x0AQMvMD+Gd3K9Zim0zZTmTHW9uyw=
github.com/farrokhi/dns v0.0.0-20230307165215-fdc21600e116/go.mod h1:jH3FNuRABq9QPC6mDdokr222D+4FmAhAR2jJwXnW0Gs=
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
github.com/pborman/getopt/v2 v2.1.0 h1:eNfR+r+dWLdWmV8g5OlpyrTYHkhVNxHBdN2cCrJmOEA=
github.com/pborman/getopt/v2 v2.1.0/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

0 comments on commit 140ec53

Please sign in to comment.