hostname/dnssecsocket/dnstrace.go

169 lines
4.2 KiB
Go

package dnssecsocket
// inspired from github.com/rs/dnstrace/main.go
import "fmt"
import "net"
// import "os"
import "strings"
import "time"
import "github.com/miekg/dns"
import "github.com/rs/dnstrace/client"
// import log "github.com/sirupsen/logrus"
// this is cool, but breaks the Windows build
// import "github.com/wercker/journalhook"
// import "github.com/davecgh/go-spew/spew"
const (
cReset = 0
cBold = 1
cRed = 31
cGreen = 32
cYellow = 33
cBlue = 34
cMagenta = 35
cCyan = 36
cGray = 37
cDarkGray = 90
)
func colorize(s interface{}, color int, enabled bool) string {
if !enabled {
return fmt.Sprintf("%v", s)
}
return fmt.Sprintf("\x1b[%dm%v\x1b[0m", color, s)
}
func Dnstrace(hostname string, qtypestr string) []dns.RR {
// color := flag.Bool("color", true, "Enable/disable colors")
color := true
qname := dns.Fqdn(hostname)
// qtype := dns.TypeA
qtype := dns.StringToType[qtypestr]
col := func(s interface{}, c int) string {
return colorize(s, c, color)
}
m := &dns.Msg{}
m.SetQuestion(qname, qtype)
// Set DNSSEC opt to better emulate the default queries from a nameserver.
o := &dns.OPT{
Hdr: dns.RR_Header{
Name: ".",
Rrtype: dns.TypeOPT,
},
}
o.SetDo()
o.SetUDPSize(dns.DefaultMsgSize)
m.Extra = append(m.Extra, o)
c := client.New(1)
c.Client.Timeout = 500 * time.Millisecond
t := client.Tracer{
GotIntermediaryResponse: func(i int, m *dns.Msg, rs client.Responses, rtype client.ResponseType) {
fr := rs.Fastest()
var r *dns.Msg
if fr != nil {
r = fr.Msg
}
qname := m.Question[0].Name
qtype := dns.TypeToString[m.Question[0].Qtype]
if i > 1 {
log(args.VerboseDnssec)
}
log(args.VerboseDnssec, "%d - query %s %s", i, qtype, qname)
if r != nil {
log(args.VerboseDnssec, ": %s", strings.Replace(strings.Replace(r.MsgHdr.String(), ";; ", "", -1), "\n", ", ", -1))
}
log(args.VerboseDnssec)
for _, pr := range rs {
ln := 0
if pr.Msg != nil {
ln = pr.Msg.Len()
}
rtt := float64(pr.RTT) / float64(time.Millisecond)
lrtt := "0ms (from cache)"
if pr.Server.HasGlue {
lrtt = "0ms (from glue)"
} else if pr.Server.LookupRTT > 0 {
lrtt = fmt.Sprintf("%.2fms", float64(pr.Server.LookupRTT)/float64(time.Millisecond))
}
log(args.VerboseDnssec, col(" - %d bytes in %.2fms + %s lookup on %s(%s)", cDarkGray), ln, rtt, lrtt, pr.Server.Name, pr.Addr)
if pr.Err != nil {
err := pr.Err
if oerr, ok := err.(*net.OpError); ok {
err = oerr.Err
}
log(args.VerboseDnssec, ": %v", col(err, cRed))
}
log(args.VerboseDnssec, "\n")
}
switch rtype {
case client.ResponseTypeDelegation:
var label string
for _, rr := range r.Ns {
if ns, ok := rr.(*dns.NS); ok {
label = ns.Header().Name
break
}
}
_, ns := c.DCache.Get(label)
for _, s := range ns {
var glue string
if s.HasGlue {
glue = col("glue: "+strings.Join(s.Addrs, ","), cDarkGray)
} else {
glue = col("no glue", cYellow)
}
log(args.VerboseDnssec, "%s %d NS %s (%s)\n", label, s.TTL, s.Name, glue)
}
case client.ResponseTypeCNAME:
for _, rr := range r.Answer {
log(args.VerboseDnssec, rr)
}
}
},
FollowingCNAME: func(domain, target string) {
log(args.VerboseDnssec, col("\n~ following CNAME %s -> %s\n", cBlue), domain, target)
},
}
r, rtt, err := c.RecursiveQuery(m, t)
if err != nil {
log(args.VerboseDnssec, col("*** error: %v\n", cRed), err)
return nil
}
log(args.VerboseDnssec)
log(args.VerboseDnssec, col(";; Cold best path time: %s\n\n", cGray), rtt)
for i, rr := range r.Answer {
log(args.VerboseDnssec, "r.Answer =", i, rr, args.VerboseDnssec)
}
return r.Answer
// for _, rr := range r.Answer {
// return rr
// }
// return nil
}
func ResolveIPv6hostname(hostname string) *net.TCPAddr {
dnsRR := Dnstrace(hostname, "AAAA")
if (dnsRR == nil) {
return nil
}
aaaa := dns.Field(dnsRR[1], 1)
localTCPAddr, _ := net.ResolveTCPAddr("tcp", aaaa)
return localTCPAddr
}
func UseJournalctl() {
log(args.VerboseDnssec, "journalhook is disabled because it breaks the Windows build right now")
// journalhook.Enable()
}