386 lines
8.6 KiB
Go
386 lines
8.6 KiB
Go
// Package main provides ...
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"dns"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// probe creates a packet and sends it to the nameserver. It
|
|
// returns a fingerprint.
|
|
func probe(c *dns.Client, addr string, f *fingerprint) *fingerprint {
|
|
m := f.msg()
|
|
r, err := c.Exchange(m, addr)
|
|
if err != nil {
|
|
return errorToFingerprint(err)
|
|
}
|
|
return toFingerprint(r)
|
|
}
|
|
|
|
// This leads to strings like: "miek.nl.,IN,A,QUERY,NOERROR,qr,aa,tc,RD,ad,cd,z,1,0,0,1,DO,4096,NSID"
|
|
type fingerprint struct {
|
|
Query dns.Question // Question to ask or Question of the reply
|
|
Error error
|
|
Opcode int
|
|
Rcode int
|
|
Response bool
|
|
Authoritative bool
|
|
Truncated bool
|
|
RecursionDesired bool
|
|
RecursionAvailable bool
|
|
AuthenticatedData bool
|
|
CheckingDisabled bool
|
|
Zero bool
|
|
Question int
|
|
Answer int
|
|
Ns int
|
|
Extra int
|
|
Do bool
|
|
UDPSize int
|
|
Nsid bool
|
|
}
|
|
|
|
// String creates a (short) string representation of a dns message.
|
|
// If a bit is set we uppercase the name 'AD' otherwise it's lowercase 'ad'.
|
|
// This leads to strings like: ".,IN,NS,QUERY,NOERROR,qr,aa,tc,RD,ad,cd,z,1,0,0,1,DO,4096,NSID"
|
|
func (f *fingerprint) String() (s string) {
|
|
if f == nil {
|
|
return "<nil>"
|
|
}
|
|
// Use the same order as in Perl's fpdns. But use much more flags.
|
|
// The Question.
|
|
if len(f.Query.Name) == 0 {
|
|
s = "."
|
|
} else {
|
|
s = f.Query.Name
|
|
}
|
|
if _, ok := dns.Class_str[f.Query.Qclass]; ok {
|
|
s += "," + dns.Class_str[f.Query.Qclass]
|
|
} else {
|
|
s += "," + "CLASS" + strconv.Itoa(int(f.Query.Qclass))
|
|
}
|
|
|
|
if _, ok := dns.Rr_str[f.Query.Qtype]; ok {
|
|
s += "," + dns.Rr_str[f.Query.Qtype]
|
|
} else {
|
|
s += "," + "TYPE" + strconv.Itoa(int(f.Query.Qtype))
|
|
}
|
|
|
|
if op, ok := dns.Opcode_str[f.Opcode]; ok {
|
|
s += "," + op
|
|
} else { // number
|
|
s += "," + valueOfInt(f.Opcode)
|
|
}
|
|
|
|
if op, ok := dns.Rcode_str[f.Rcode]; ok {
|
|
s += "," + op
|
|
} else { // number
|
|
s += "," + valueOfInt(f.Rcode)
|
|
}
|
|
|
|
s += valueOfBool(f.Response, ",qr")
|
|
s += valueOfBool(f.Authoritative, ",aa")
|
|
s += valueOfBool(f.Truncated, ",tc")
|
|
s += valueOfBool(f.RecursionDesired, ",rd")
|
|
s += valueOfBool(f.RecursionAvailable, ",ra")
|
|
s += valueOfBool(f.AuthenticatedData, ",ad")
|
|
s += valueOfBool(f.CheckingDisabled, ",cd")
|
|
s += valueOfBool(f.Zero, ",z")
|
|
|
|
s += "," + valueOfInt(f.Question)
|
|
s += "," + valueOfInt(f.Answer)
|
|
s += "," + valueOfInt(f.Ns)
|
|
s += "," + valueOfInt(f.Extra)
|
|
|
|
s += valueOfBool(f.Do, ",do")
|
|
s += "," + valueOfInt(f.UDPSize)
|
|
s += valueOfBool(f.Nsid, ",nsid")
|
|
|
|
return s
|
|
}
|
|
|
|
// Return a new fingerprint, set from string
|
|
func newFingerprint(s string) *fingerprint {
|
|
f := new(fingerprint)
|
|
f.setString(s)
|
|
return f
|
|
}
|
|
|
|
// SetString sets the strings str to the fingerprint *f.
|
|
func (f *fingerprint) setString(str string) {
|
|
for i, s := range strings.Split(str, ",") {
|
|
switch i {
|
|
case 0: // question section domain name
|
|
f.Query.Name = s
|
|
case 1: // Qclass
|
|
f.Query.Qclass = 0
|
|
if c, ok := dns.Str_class[s]; ok {
|
|
f.Query.Qclass = c
|
|
}
|
|
case 2: // Qtype
|
|
f.Query.Qtype = 0
|
|
if c, ok := dns.Str_rr[s]; ok {
|
|
f.Query.Qtype = c
|
|
}
|
|
case 3:
|
|
if op, ok := dns.Str_opcode[s]; ok {
|
|
f.Opcode = op
|
|
} else { // number
|
|
f.Opcode = valueOfString(s)
|
|
}
|
|
case 4:
|
|
if op, ok := dns.Str_rcode[s]; ok {
|
|
f.Rcode = op
|
|
} else { // number
|
|
f.Rcode = valueOfString(s)
|
|
}
|
|
case 5:
|
|
f.Response = s == strings.ToUpper("qr")
|
|
case 6:
|
|
f.Authoritative = s == strings.ToUpper("aa")
|
|
case 7:
|
|
f.Truncated = s == strings.ToUpper("tc")
|
|
case 8:
|
|
f.RecursionDesired = s == strings.ToUpper("rd")
|
|
case 9:
|
|
f.RecursionAvailable = s == strings.ToUpper("ra")
|
|
case 10:
|
|
f.AuthenticatedData = s == strings.ToUpper("ad")
|
|
case 11:
|
|
f.CheckingDisabled = s == strings.ToUpper("cd")
|
|
case 12:
|
|
f.Zero = s == strings.ToUpper("z")
|
|
case 13:
|
|
f.Question = valueOfString(s)
|
|
case 14:
|
|
f.Answer = valueOfString(s)
|
|
case 15:
|
|
f.Ns = valueOfString(s)
|
|
case 16:
|
|
f.Extra = valueOfString(s)
|
|
case 17:
|
|
f.Do = s == strings.ToUpper("do")
|
|
case 18:
|
|
f.UDPSize = valueOfString(s)
|
|
case 19:
|
|
f.Nsid = s == strings.ToUpper("nsid")
|
|
default:
|
|
panic("unhandled fingerprint field")
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (f *fingerprint) ok() bool {
|
|
return f.Error == nil
|
|
}
|
|
|
|
func (f *fingerprint) error() string {
|
|
if f.Error == nil {
|
|
panic("error is nil")
|
|
}
|
|
return f.Error.Error()
|
|
}
|
|
|
|
func errorToFingerprint(e error) *fingerprint {
|
|
f := new(fingerprint)
|
|
f.Error = e
|
|
return f
|
|
}
|
|
|
|
// Convert a Msg to a fingerprint
|
|
func toFingerprint(m *dns.Msg) *fingerprint {
|
|
if m == nil {
|
|
return nil
|
|
}
|
|
h := m.MsgHdr
|
|
f := new(fingerprint)
|
|
|
|
if len(m.Question) > 0 {
|
|
if len(m.Question[0].Name) == 0 {
|
|
f.Query.Name = "."
|
|
} else {
|
|
f.Query.Name = m.Question[0].Name
|
|
}
|
|
f.Query.Qtype = m.Question[0].Qtype
|
|
f.Query.Qclass = m.Question[0].Qclass
|
|
} else {
|
|
// Default, nil values
|
|
f.Query.Name = "."
|
|
f.Query.Qtype = 0
|
|
f.Query.Qclass = 0
|
|
}
|
|
|
|
f.Opcode = h.Opcode
|
|
f.Rcode = h.Rcode
|
|
f.Response = h.Response
|
|
f.Authoritative = h.Authoritative
|
|
f.Truncated = h.Truncated
|
|
f.RecursionDesired = h.RecursionDesired
|
|
f.RecursionAvailable = h.RecursionAvailable
|
|
f.AuthenticatedData = h.AuthenticatedData
|
|
f.CheckingDisabled = h.CheckingDisabled
|
|
f.Zero = h.Zero
|
|
|
|
f.Question = len(m.Question)
|
|
f.Answer = len(m.Answer)
|
|
f.Ns = len(m.Ns)
|
|
f.Extra = len(m.Extra)
|
|
f.Do = false
|
|
f.UDPSize = 0
|
|
|
|
for _, r := range m.Extra {
|
|
if r.Header().Rrtype == dns.TypeOPT {
|
|
// version is always 0 - and I cannot set it anyway
|
|
f.Do = r.(*dns.RR_OPT).Do()
|
|
f.UDPSize = int(r.(*dns.RR_OPT).UDPSize())
|
|
if len(r.(*dns.RR_OPT).Option) == 1 {
|
|
// Only support NSID atm
|
|
f.Nsid = r.(*dns.RR_OPT).Option[0].Option() == dns.EDNS0NSID
|
|
}
|
|
}
|
|
}
|
|
return f
|
|
}
|
|
|
|
// Create a dns message from a fingerprint string and
|
|
// a DNS question. The order of a string is always the same.
|
|
// .,IN,NS,QUERY,NOERROR,qr,aa,tc,RD,ad,ad,z,1,0,0,1,DO,4096,nsid
|
|
func (f *fingerprint) msg() *dns.Msg {
|
|
m := new(dns.Msg)
|
|
m.MsgHdr.Id = dns.Id()
|
|
m.Question = make([]dns.Question, 1)
|
|
m.Question[0] = dns.Question{f.Query.Name, f.Query.Qtype, f.Query.Qclass}
|
|
m.MsgHdr.Opcode = f.Opcode
|
|
m.MsgHdr.Rcode = f.Rcode
|
|
m.MsgHdr.Response = f.Response
|
|
m.MsgHdr.Authoritative = f.Authoritative
|
|
m.MsgHdr.Truncated = f.Truncated
|
|
m.MsgHdr.RecursionDesired = f.RecursionDesired
|
|
m.MsgHdr.AuthenticatedData = f.AuthenticatedData
|
|
m.MsgHdr.CheckingDisabled = f.CheckingDisabled
|
|
m.MsgHdr.Zero = f.Zero
|
|
|
|
if f.Do {
|
|
// Add an OPT section.
|
|
m.SetEdns0(0, true)
|
|
// We have added an OPT RR, set the size.
|
|
m.Extra[0].(*dns.RR_OPT).SetUDPSize(uint16(f.UDPSize))
|
|
if f.Nsid {
|
|
e := new(dns.EDNS0_NSID)
|
|
e.Code = dns.EDNS0NSID
|
|
m.Extra[0].(*dns.RR_OPT).Option = append(m.Extra[0].(*dns.RR_OPT).Option, e)
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
// Compare two fingerprints, it returns the number of differences.
|
|
func (f *fingerprint) compare(f1 *fingerprint) (diff int) {
|
|
if f.Query.Name != f1.Query.Name {
|
|
diff++
|
|
}
|
|
if f.Query.Qtype != f1.Query.Qtype {
|
|
diff++
|
|
}
|
|
if f.Query.Qclass != f1.Query.Qclass {
|
|
diff++
|
|
}
|
|
if f.Opcode != f1.Opcode {
|
|
diff++
|
|
}
|
|
if f.Rcode != f1.Rcode {
|
|
diff++
|
|
}
|
|
if f.Response != f1.Response {
|
|
diff++
|
|
}
|
|
if f.Authoritative != f1.Authoritative {
|
|
diff++
|
|
}
|
|
if f.Truncated != f1.Truncated {
|
|
diff++
|
|
}
|
|
if f.RecursionDesired != f1.RecursionDesired {
|
|
diff++
|
|
}
|
|
if f.RecursionAvailable != f1.RecursionAvailable {
|
|
diff++
|
|
}
|
|
if f.AuthenticatedData != f1.AuthenticatedData {
|
|
diff++
|
|
}
|
|
if f.CheckingDisabled != f1.CheckingDisabled {
|
|
diff++
|
|
}
|
|
if f.Zero != f1.Zero {
|
|
diff++
|
|
}
|
|
if f.Question != f1.Question {
|
|
diff++
|
|
}
|
|
if f.Answer != f1.Answer {
|
|
diff++
|
|
}
|
|
if f.Ns != f1.Ns {
|
|
diff++
|
|
}
|
|
if f.Extra != f1.Extra {
|
|
diff++
|
|
}
|
|
if f.Do != f1.Do {
|
|
diff++
|
|
}
|
|
if f.UDPSize != f1.UDPSize {
|
|
diff++
|
|
}
|
|
if f.Nsid != f1.Nsid {
|
|
diff++
|
|
}
|
|
return
|
|
}
|
|
|
|
// Read the finger prints from the file. Lines starting with
|
|
// # are comments. Each line should contain one fingerprint in string format.
|
|
func fingerPrintFromFile(f string) ([]*fingerprint, error) {
|
|
r, e := os.Open(f)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
b := bufio.NewReader(r)
|
|
l, p, e := b.ReadLine()
|
|
i := 1
|
|
prints := make([]*fingerprint, 0)
|
|
for e == nil {
|
|
if p {
|
|
return nil, nil
|
|
}
|
|
// Comments and empty lines are ignored
|
|
if l[0] != '#' && l[0] != '\n' {
|
|
prints = append(prints, newFingerprint(string(l)))
|
|
}
|
|
l, p, e = b.ReadLine()
|
|
i++
|
|
}
|
|
return prints, nil
|
|
}
|
|
|
|
func valueOfBool(b bool, w string) string {
|
|
if b {
|
|
return strings.ToUpper(w)
|
|
}
|
|
return strings.ToLower(w)
|
|
}
|
|
|
|
func valueOfInt(i int) string {
|
|
return strconv.Itoa(i)
|
|
}
|
|
|
|
func valueOfString(s string) int {
|
|
i, _ := strconv.Atoi(s)
|
|
return i
|
|
}
|