github.com/phuslu/fastdns@v0.8.3-0.20240310041952-69506fc67dd1/cmd/fastdig/fastdig.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"net/netip"
     7  	"os"
     8  	"regexp"
     9  	"runtime"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/phuslu/fastdns"
    14  )
    15  
    16  func main() {
    17  	domain, qtype, server, options := parse(os.Args[1:])
    18  
    19  	client := &fastdns.Client{
    20  		AddrPort:    netip.AddrPortFrom(netip.MustParseAddr(server), 53),
    21  		ReadTimeout: 2 * time.Second,
    22  		MaxConns:    1000,
    23  	}
    24  
    25  	req, resp := fastdns.AcquireMessage(), fastdns.AcquireMessage()
    26  	defer fastdns.ReleaseMessage(req)
    27  	defer fastdns.ReleaseMessage(resp)
    28  
    29  	req.SetRequestQuestion(domain, fastdns.ParseType(qtype), fastdns.ClassINET)
    30  
    31  	start := time.Now()
    32  	err := client.Exchange(req, resp)
    33  	if err != nil {
    34  		fmt.Fprintf(os.Stderr, "client=%+v exchange(\"%s\") error: %+v\n", client, domain, err)
    35  		os.Exit(1)
    36  	}
    37  	end := time.Now()
    38  
    39  	if opt("raw", options) {
    40  		fmt.Printf("%x\n", resp.Raw)
    41  	}
    42  
    43  	if opt("short", options) {
    44  		short(resp)
    45  	} else {
    46  		cmd(req, resp, server, start, end)
    47  	}
    48  }
    49  
    50  func parse(args []string) (domain, qtype, server string, options []string) {
    51  	for i := 0; i < len(args); i++ {
    52  		arg := args[i]
    53  		if arg == "" {
    54  			continue
    55  		}
    56  		switch arg[0] {
    57  		case '@':
    58  			server = arg[1:]
    59  		case '+':
    60  			options = append(options, arg[1:])
    61  		default:
    62  			if domain == "" {
    63  				domain = arg
    64  			} else {
    65  				qtype = arg
    66  			}
    67  		}
    68  	}
    69  	if server == "" {
    70  		server = "8.8.8.8"
    71  		if data, err := os.ReadFile("/etc/resolv.conf"); err == nil {
    72  			if m := regexp.MustCompile(`(^|\n)\s*nameserver\s+(\S+)`).FindAllStringSubmatch(string(data), -1); len(m) != 0 {
    73  				server = m[0][2]
    74  			}
    75  		}
    76  	}
    77  	if qtype == "" {
    78  		qtype = "A"
    79  	}
    80  	if fastdns.ParseType(qtype) == 0 && fastdns.ParseType(domain) != 0 {
    81  		domain, qtype = qtype, domain
    82  	}
    83  	return
    84  }
    85  
    86  func opt(option string, options []string) bool {
    87  	for _, s := range options {
    88  		if s == option {
    89  			return true
    90  		}
    91  	}
    92  	return false
    93  }
    94  
    95  func short(resp *fastdns.Message) {
    96  	_ = resp.Walk(func(name []byte, typ fastdns.Type, class fastdns.Class, ttl uint32, data []byte) bool {
    97  		var v interface{}
    98  		switch typ {
    99  		case fastdns.TypeA, fastdns.TypeAAAA:
   100  			v, _ = netip.AddrFromSlice(data)
   101  		case fastdns.TypeCNAME, fastdns.TypeNS:
   102  			v = fmt.Sprintf("%s.", resp.DecodeName(nil, data))
   103  		case fastdns.TypeMX:
   104  			v = fmt.Sprintf("%d %s.", binary.BigEndian.Uint16(data), resp.DecodeName(nil, data[2:]))
   105  		case fastdns.TypeTXT:
   106  			v = fmt.Sprintf("\"%s\"", data[1:])
   107  		case fastdns.TypeSRV:
   108  			priority := binary.BigEndian.Uint16(data)
   109  			weight := binary.BigEndian.Uint16(data[2:])
   110  			port := binary.BigEndian.Uint16(data[4:])
   111  			target := resp.DecodeName(nil, data[6:])
   112  			v = fmt.Sprintf("%d %d %d %s.", priority, weight, port, target)
   113  		case fastdns.TypeSOA:
   114  			var mname []byte
   115  			for i, b := range data {
   116  				if b == 0 {
   117  					mname = data[:i+1]
   118  					break
   119  				} else if b&0b11000000 == 0b11000000 {
   120  					mname = data[:i+2]
   121  					break
   122  				}
   123  			}
   124  			nname := resp.DecodeName(nil, data[len(mname):len(data)-20])
   125  			mname = resp.DecodeName(nil, mname)
   126  			serial := binary.BigEndian.Uint32(data[len(data)-20:])
   127  			refresh := binary.BigEndian.Uint32(data[len(data)-16:])
   128  			retry := binary.BigEndian.Uint32(data[len(data)-12:])
   129  			expire := binary.BigEndian.Uint32(data[len(data)-8:])
   130  			minimum := binary.BigEndian.Uint32(data[len(data)-4:])
   131  			v = fmt.Sprintf("%s. %s. %d %d %d %d %d", mname, nname, serial, refresh, retry, expire, minimum)
   132  		default:
   133  			v = fmt.Sprintf("%x", data)
   134  		}
   135  		fmt.Printf("%s\n", v)
   136  		return true
   137  	})
   138  }
   139  
   140  func cmd(req, resp *fastdns.Message, server string, start, end time.Time) {
   141  	var flags string
   142  	for _, f := range []struct {
   143  		b byte
   144  		s string
   145  	}{
   146  		{resp.Header.Flags.QR(), "qr"},
   147  		{resp.Header.Flags.AA(), "aa"},
   148  		{resp.Header.Flags.TC(), "tc"},
   149  		{resp.Header.Flags.RD(), "rd"},
   150  		{resp.Header.Flags.RA(), "ra"},
   151  	} {
   152  		if f.b == 0 {
   153  			continue
   154  		}
   155  		flags += f.s + " "
   156  	}
   157  	flags = strings.TrimSpace(flags)
   158  
   159  	fmt.Printf("\n")
   160  	fmt.Printf("; <<>> DiG 0.0.1-fastdns-%s <<>> %s\n", runtime.Version(), req.Domain)
   161  	fmt.Printf(";; global options: +cmd +noedns\n")
   162  	fmt.Printf(";; Got answer:\n")
   163  	fmt.Printf(";; ->>HEADER<<- opcode: %s, status: %s, id: %d\n",
   164  		strings.ToUpper(resp.Header.Flags.Opcode().String()), strings.ToUpper(resp.Header.Flags.Rcode().String()), resp.Header.ID)
   165  	fmt.Printf(";; flags: %s; QUERY: %d, ANSWER: %d, AUTHORITY: %d, ADDITIONAL: %d\n",
   166  		flags, resp.Header.QDCount, resp.Header.ANCount, resp.Header.NSCount, resp.Header.ARCount)
   167  
   168  	fmt.Printf("\n")
   169  	// fmt.Printf(";; OPT PSEUDOSECTION:\n")
   170  	// fmt.Printf("; EDNS: version: 0, flags:; udp: 512\n")
   171  	fmt.Printf(";; QUESTION SECTION:\n")
   172  	fmt.Printf(";%s.		%s	%s\n", req.Domain, req.Question.Class, req.Question.Type)
   173  
   174  	fmt.Printf("\n")
   175  	if resp.Header.ANCount > 0 {
   176  		fmt.Printf(";; ANSWER SECTION:\n")
   177  	} else {
   178  		fmt.Printf(";; AUTHORITY SECTION:\n")
   179  	}
   180  	_ = resp.Walk(func(name []byte, typ fastdns.Type, class fastdns.Class, ttl uint32, data []byte) bool {
   181  		var v interface{}
   182  		switch typ {
   183  		case fastdns.TypeA, fastdns.TypeAAAA:
   184  			v, _ = netip.AddrFromSlice(data)
   185  		case fastdns.TypeCNAME, fastdns.TypeNS:
   186  			v = fmt.Sprintf("%s.", resp.DecodeName(nil, data))
   187  		case fastdns.TypeMX:
   188  			v = fmt.Sprintf("%d %s.", binary.BigEndian.Uint16(data), resp.DecodeName(nil, data[2:]))
   189  		case fastdns.TypeTXT:
   190  			v = fmt.Sprintf("\"%s\"", data[1:])
   191  		case fastdns.TypeSRV:
   192  			priority := binary.BigEndian.Uint16(data)
   193  			weight := binary.BigEndian.Uint16(data[2:])
   194  			port := binary.BigEndian.Uint16(data[4:])
   195  			target := resp.DecodeName(nil, data[6:])
   196  			v = fmt.Sprintf("%d %d %d %s.", priority, weight, port, target)
   197  		case fastdns.TypeSOA:
   198  			var mname []byte
   199  			for i, b := range data {
   200  				if b == 0 {
   201  					mname = data[:i+1]
   202  					break
   203  				} else if b&0b11000000 == 0b11000000 {
   204  					mname = data[:i+2]
   205  					break
   206  				}
   207  			}
   208  			nname := resp.DecodeName(nil, data[len(mname):len(data)-20])
   209  			mname = resp.DecodeName(nil, mname)
   210  			serial := binary.BigEndian.Uint32(data[len(data)-20:])
   211  			refresh := binary.BigEndian.Uint32(data[len(data)-16:])
   212  			retry := binary.BigEndian.Uint32(data[len(data)-12:])
   213  			expire := binary.BigEndian.Uint32(data[len(data)-8:])
   214  			minimum := binary.BigEndian.Uint32(data[len(data)-4:])
   215  			v = fmt.Sprintf("%s. %s. %d %d %d %d %d", mname, nname, serial, refresh, retry, expire, minimum)
   216  		default:
   217  			v = fmt.Sprintf("%x", data)
   218  		}
   219  		fmt.Printf("%s.	%d	%s	%s	%s\n", resp.DecodeName(nil, name), ttl, class, typ, v)
   220  		return true
   221  	})
   222  
   223  	fmt.Printf("\n")
   224  	fmt.Printf(";; Query time: %d msec\n", end.Sub(start)/time.Millisecond)
   225  	fmt.Printf(";; SERVER: %s#53(%s)\n", server, server)
   226  	fmt.Printf(";; WHEN: %s\n", start.Format("Mon Jan 02 15:04:05 MST 2006"))
   227  	fmt.Printf(";; MSG SIZE  rcvd: %d\n", len(resp.Raw))
   228  	fmt.Printf("\n")
   229  }