commit 66580aad7489cb052d74e91551c83c90eb628763 Author: MDK Date: Wed Feb 21 15:28:24 2024 +0800 initial commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..aa04e65 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module fpdns_client + +go 1.20 + +require github.com/miekg/dns v1.1.58 + +require ( + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/tools v0.17.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c903875 --- /dev/null +++ b/go.sum @@ -0,0 +1,11 @@ +github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= +github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= diff --git a/main.go b/main.go new file mode 100644 index 0000000..f951875 --- /dev/null +++ b/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + "fpdns_client/methods" +) + +func main() { + _, err := methods.BehaviorTest("127.0.0.1", "127.0.0.1", "8888", "echodns.xyz") + if err != nil { + fmt.Println(err) + } +} diff --git a/methods/test_methods.go b/methods/test_methods.go new file mode 100644 index 0000000..9499590 --- /dev/null +++ b/methods/test_methods.go @@ -0,0 +1,96 @@ +package methods + +import ( + "errors" + "fmt" + "fpdns_client/utils" + "strconv" + "time" + + "github.com/miekg/dns" +) + +func ProbeStart(addr string, sld string) (int, error) { + time_now := strconv.FormatInt(time.Now().Unix(), 10) + domain := dns.Fqdn("start-" + time_now + "." + sld) + res, err := utils.DNSQuery(addr, domain, false, 1, 1, false) + if err == nil { + if len(res.Answer) == 1 { + if a, ok := res.Answer[0].(*dns.A); ok { + ip_str := a.A.String() + ip_val, _ := utils.IPv4ToInt(ip_str) + return int(ip_val), nil + } + } + } else { + return 0, err + } + return 0, errors.New("wrong token response") +} + +func InitProbe(addr string, token int, sld string) error { + time_now := strconv.FormatInt(time.Now().Unix(), 10) + domain := dns.Fqdn("init-" + time_now + "-" + strconv.Itoa(token) + "." + sld) + res, err := utils.DNSQuery(addr, domain, true, 1, 1, false) + if err != nil { + return err + } else { + if len(res.Answer) == 1 { + if a, ok := res.Answer[0].(*dns.A); ok { + ip_str := a.A.String() + if ip_str == "200.200.200.200" { + return nil + } + } else { + return errors.New("invalid Reply Code") + } + } + } + return errors.New("init query failure") +} + +func ProbeTerminate(addr string, token int, sld string) error { + time_now := strconv.FormatInt(time.Now().Unix(), 10) + domain := dns.Fqdn("end-" + time_now + "-" + strconv.Itoa(token) + "." + sld) + res, err := utils.DNSQuery(addr, domain, false, 1, 1, false) + if err != nil { + return err + } else { + if len(res.Answer) == 1 { + if a, ok := res.Answer[0].(*dns.A); ok { + ip_str := a.A.String() + if ip_str == "200.200.200.200" { + return nil + } + } else { + return errors.New("invalid Reply Code") + } + } + } + return errors.New("terminate query failure") +} + +func BehaviorTest(target string, server string, api_port string, sld string) (string, error) { + target_addr := target + ":53" + server_addr := server + ":53" + result_addr := server + ":" + api_port + token, err := ProbeStart(server_addr, sld) + if err != nil { + return "", err + } + err = InitProbe(target_addr, token, sld) + if err != nil { + return "", err + } + err = ProbeTerminate(server_addr, token, sld) + if err != nil { + return "", err + } + data, err := utils.GetResult(result_addr, token) + if err != nil { + return "", err + } + json_str, _ := utils.OutputJson(data) + fmt.Println(json_str) + return "", nil +} diff --git a/utils/dns_utils.go b/utils/dns_utils.go new file mode 100644 index 0000000..60e3d62 --- /dev/null +++ b/utils/dns_utils.go @@ -0,0 +1,31 @@ +// dns utils +package utils + +import ( + "github.com/miekg/dns" +) + +// build the question section of a dns packet +func QuestionMaker(domain string, qclass uint16, qtype uint16) *dns.Question { + return &dns.Question{Name: dns.Fqdn(domain), Qtype: qtype, Qclass: qclass} +} + +// build a specific query message +func QueryMaker(domain string, rd bool, qclass uint16, qtype uint16, edns bool) *dns.Msg { + msg := new(dns.Msg) + msg.Id = dns.Id() + msg.RecursionDesired = rd + msg.Question = make([]dns.Question, 1) + msg.Question[0] = *QuestionMaker(domain, qclass, qtype) + if edns { + msg = msg.SetEdns0(4096, false) + } + return msg +} + +// query and receive the response +func DNSQuery(addr string, domain string, rd bool, qclass uint16, qtype uint16, edns bool) (*dns.Msg, error) { + msg := QueryMaker(domain, rd, qclass, qtype, edns) + res, err := dns.Exchange(msg, addr) + return res, err +} diff --git a/utils/other_utils.go b/utils/other_utils.go new file mode 100644 index 0000000..a25dcdc --- /dev/null +++ b/utils/other_utils.go @@ -0,0 +1,68 @@ +package utils + +import ( + "encoding/json" + "fmt" + "net" + "net/http" + "strconv" + "strings" +) + +type ResolverBehavior struct { + Dnssec bool `json:"dnssec"` + QnameEncode bool `json:"0x20"` + QueryMerge int `json:"merge_dup"` + MaxNsDepth int `json:"max_ns_depth"` + MaxCnameDepth int `json:"max_cname_depth"` + RetryLimit int `json:"retry_limit"` + FetchLimit int `json:"fetch_limit"` + TimeoutStart int64 `json:"timeout_start"` + TimeoutEnd int64 `json:"timeout_end"` +} + +func IsValidIP(ip string) bool { + res := net.ParseIP(ip) + return res != nil +} + +func IPv4ToInt(ip string) (uint32, error) { + if !IsValidIP(ip) { + return 0, fmt.Errorf("invalid IP address: %s", ip) + } + labels := strings.Split(ip, ".") + var result uint32 + for _, label := range labels { + label_val, _ := strconv.Atoi(label) + result = (result << 8) | uint32(label_val) + } + return result, nil +} + +func GetResult(addr string, token int) (*ResolverBehavior, error) { + data := new(ResolverBehavior) + url := "http://" + addr + "/results/" + strconv.Itoa(token) + response, err := http.Get(url) + if err != nil { + return data, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return data, fmt.Errorf("wrong HTTP status code : %v", response.StatusCode) + } + + err = json.NewDecoder(response.Body).Decode(data) + if err != nil { + return data, err + } + return data, nil +} + +func OutputJson(data interface{}) (string, error) { + json_byte, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(json_byte), nil +}