package dial import ( "fmt" "io/ioutil" "net" "net/http" "net/url" "os" "path/filepath" "strconv" "strings" "time" "github.com/miekg/dns" "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/json" ) const ( httpDnsUrl = "https://dig.bdurl.net/q" googleDnsUrl = "https://dns.google/resolve" ) const ( DnsSourceTypeLocal = iota DnsSourceTypeHttp DnsSourceTypeGoogle ) const ( DnsRecordTypeA = 1 DnsRecordTypeAAAA = 28 DnsRecordTypeCNAME = 5 ) var dnsHttpClient = &http.Client{ Timeout: 5 * time.Minute, } type DnsOptions struct { DnsSourceType int DnsRecordType int DnsServer string SaveTests bool } type DnsResult struct { DnsList []string `json:"dnsList"` DnsSource int `json:"dnsType"` DnsRecordType int `json:"dnsRecordType"` DnsServer string `json:"dnsServer,omitempty"` Ttl int `json:"ttl"` Suc bool `json:"suc"` ErrMsg string `json:"errMsg"` } type googleDnsResp struct { Answer []googleDnsAnswer `json:"Answer"` } type httpDnsResp struct { Ips []string `json:"ips"` Ttl int `json:"ttl"` } type googleDnsAnswer struct { Name string `json:"name"` Type int `json:"type"` TTL int `json:"TTL"` Data string `json:"data"` } func ParseIP(s string) (net.IP, int) { ip := net.ParseIP(s) if ip == nil { return nil, 0 } for i := 0; i < len(s); i++ { switch s[i] { case '.': return ip, 4 case ':': return ip, 6 } } return nil, 0 } func localDns(src string, dnsRecordType int, dnsServer string) (dnsResult DnsResult, err error) { dnsResult.DnsSource = DnsSourceTypeLocal dnsResult.DnsRecordType = dnsRecordType if dnsServer == "" { config, _ := dns.ClientConfigFromFile("/etc/resolv.conf") dnsServer = config.Servers[0] } else { dnsResult.DnsServer = dnsServer } _, ipType := ParseIP(dnsServer) if ipType == 4 { dnsServer += ":53" } c := dns.Client{ Timeout: 5 * time.Second, } m := dns.Msg{} m.SetQuestion(src+".", uint16(dnsRecordType)) r, _, err := c.Exchange(&m, dnsServer) if err != nil { return } for _, ans := range r.Answer { switch dnsRecordType { case DnsRecordTypeA: record, isType := ans.(*dns.A) if isType { dnsResult.Ttl = int(record.Hdr.Ttl) dnsResult.DnsList = append(dnsResult.DnsList, record.A.String()) } case DnsRecordTypeAAAA: record, isType := ans.(*dns.AAAA) if isType { dnsResult.Ttl = int(record.Hdr.Ttl) dnsResult.DnsList = append(dnsResult.DnsList, record.AAAA.String()) } case DnsRecordTypeCNAME: record, isType := ans.(*dns.CNAME) if isType { dnsResult.Ttl = int(record.Hdr.Ttl) dnsResult.DnsList = append(dnsResult.DnsList, record.Target) } } } return } func httpDns(url string, dnsRecordType int) (dnsResult DnsResult, err error) { target := httpDnsUrl + "?host=" + url if dnsRecordType == DnsRecordTypeAAAA { target += "&aid=13&f=2" } resp, err := dnsHttpClient.Get(target) dnsResult.DnsSource = DnsSourceTypeHttp dnsResult.DnsRecordType = dnsRecordType if err != nil { return } defer resp.Body.Close() var buf []byte buf, err = ioutil.ReadAll(resp.Body) if err != nil { return } var result httpDnsResp err = json.Unmarshal(buf, &result) if err != nil { return } dnsResult.DnsList = result.Ips dnsResult.Ttl = result.Ttl return } func googleDns(url string, dnsRecordType int) (dnsResult DnsResult, err error) { resp, err := dnsHttpClient.Get(googleDnsUrl + "?name=" + url + "&type=" + strconv.Itoa(dnsRecordType)) dnsResult.DnsSource = DnsSourceTypeGoogle dnsResult.DnsRecordType = dnsRecordType if err != nil { return } defer resp.Body.Close() var buf []byte buf, err = ioutil.ReadAll(resp.Body) if err != nil { return } var result googleDnsResp err = json.Unmarshal(buf, &result) if err != nil { return } if len(result.Answer) == 0 { return } for _, answer := range result.Answer { if answer.Type == dnsRecordType { dnsResult.Ttl = answer.TTL dnsResult.DnsList = append(dnsResult.DnsList, answer.Data) } } return } func DoDns(dnsOptions *DnsOptions, args []string) (err error) { if len(args) != 1 { return errors.New("there should be one argument") } var dnsResult DnsResult defer func() { if dnsOptions.SaveTests { dir, _ := os.Getwd() dnsResultName := fmt.Sprintf("dns_result_%v.json", time.Now().Format("20060102150405")) dnsResultPath := filepath.Join(dir, dnsResultName) err = builtin.Dump2JSON(dnsResult, dnsResultPath) if err != nil { log.Error().Err(err).Msg("save dns resolution result failed") } } }() dnsTarget := args[0] parsedURL, err := url.Parse(dnsTarget) if err == nil && parsedURL.Host != "" { log.Info().Msgf("parse input url %v and extract host %v", dnsTarget, parsedURL.Host) dnsTarget = strings.Split(parsedURL.Host, ":")[0] } log.Info().Msgf("resolve DNS for %v", dnsTarget) dnsRecordType := dnsOptions.DnsRecordType dnsServer := dnsOptions.DnsServer switch dnsOptions.DnsSourceType { case DnsSourceTypeLocal: dnsResult, err = localDns(dnsTarget, dnsRecordType, dnsServer) case DnsSourceTypeHttp: dnsResult, err = httpDns(dnsTarget, dnsRecordType) case DnsSourceTypeGoogle: dnsResult, err = googleDns(dnsTarget, dnsRecordType) } if err != nil { dnsResult.Suc = false dnsResult.ErrMsg = err.Error() log.Error().Err(err).Msgf("fail to do DNS for %s", dnsTarget) } else { dnsResult.Suc = true dnsResult.ErrMsg = "" fmt.Printf("\nDNS resolution done, result IP list: %v\n", dnsResult.DnsList) } return }