From 7115311c42ccd8a7565a5f8728bcaa18bf20bf02 Mon Sep 17 00:00:00 2001 From: MDK Date: Mon, 25 Mar 2024 17:15:25 +0800 Subject: [PATCH] programs for edns, svcb, https support measurement --- .gitignore | 1 + go.mod | 12 +++++ go.sum | 11 ++++ main.go | 38 +++++++++++++ method/combined.go | 123 +++++++++++++++++++++++++++++++++++++++++++ method/edns.go | 76 ++++++++++++++++++++++++++ method/svcb.go | 63 ++++++++++++++++++++++ test/test-input | 100 +++++++++++++++++++++++++++++++++++ utils/dns_utils.go | 62 ++++++++++++++++++++++ utils/other_utils.go | 31 +++++++++++ 10 files changed, 517 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 method/combined.go create mode 100644 method/edns.go create mode 100644 method/svcb.go create mode 100644 test/test-input create mode 100644 utils/dns_utils.go create mode 100644 utils/other_utils.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf0824e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.log \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c57fc62 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module edns_svcb + +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..e64078e --- /dev/null +++ b/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "edns_svcb/method" + "edns_svcb/utils" + "flag" + "log" + "os" + "sync" + "time" +) + +func main() { + var input_file string + flag.StringVar(&input_file, "input", "", "input file name") + flag.Parse() + input_pool := make(chan string, 500) + result_pool := make(chan method.CombinedResult) + var test_tasks sync.WaitGroup + var process_tasks sync.WaitGroup + + logfilename := "test/" + time.Now().Format("2006-01-02_15-04-05") + ".log" + logfile, err := os.OpenFile(logfilename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + log.Fatal("Failed to create log file: ", err) + } + defer logfile.Close() + logger := log.New(logfile, "", log.LstdFlags) + + go utils.RetrieveLines(input_pool, input_file) + method.CombinedMeasurement(input_pool, result_pool, 500, &test_tasks, logger) + process_tasks.Add(1) + go method.CombinedResultProcess(result_pool, &process_tasks) + test_tasks.Wait() + close(result_pool) + process_tasks.Wait() + time.Sleep(time.Second) +} diff --git a/method/combined.go b/method/combined.go new file mode 100644 index 0000000..f264710 --- /dev/null +++ b/method/combined.go @@ -0,0 +1,123 @@ +package method + +import ( + "edns_svcb/utils" + "fmt" + "log" + "sync" + + "github.com/miekg/dns" +) + +type CombinedResult struct { + Alive bool + EDNSSupport bool + SVCBSupport bool + HTTPSSupport bool + Err error +} + +func CombinedMeasurement(ip_pool chan string, result_pool chan CombinedResult, routine_num int, wg *sync.WaitGroup, logger *log.Logger) { + for i := 0; i < routine_num; i++ { + wg.Add(1) + go CombinedTestRoutine(ip_pool, result_pool, wg, logger) + } +} + +func CombinedTestRoutine(ip_pool chan string, result_pool chan CombinedResult, wg *sync.WaitGroup, logger *log.Logger) { + for { + if ip, ok := <-ip_pool; ok { + addr := ip + ":53" + res := CombinedTest(addr) + result_pool <- res + if res.Err != nil { + logger.Printf("%v : failed ( %v )", ip, res.Err) + } else { + logger.Printf("%v : alive %v edns %v svcb %v https %v", ip, res.Alive, res.EDNSSupport, res.SVCBSupport, res.HTTPSSupport) + } + } else { + break + } + } + wg.Done() +} + +func CombinedTest(addr string) CombinedResult { + result := CombinedResult{} + _, err := utils.DNSQuery(addr, utils.DNSOptions{Domain: "www.example.com", RD: true}) + if err != nil { + return CombinedResult{Err: err} + } + result.Alive = true + + //edns support + res, err := utils.DNSQuery(addr, utils.DNSOptions{Domain: "www.example.com", EDNS: true, RD: true}) + if err == nil { + if len(res.Extra) > 0 { + if _, ok := res.Extra[0].(*dns.OPT); ok { + result.EDNSSupport = true + } + } + } else { + result.Err = err + return result + } + + // svcb support + res, err = utils.DNSQuery(addr, utils.DNSOptions{Domain: "_dns.resolver.arpa", Qtype: dns.TypeSVCB, RD: true}) + if err == nil { + if len(res.Answer) > 0 { + if _, ok := res.Answer[0].(*dns.SVCB); ok { + result.SVCBSupport = true + } + } + } else { + result.Err = err + return result + } + + // https support + res, err = utils.DNSQuery(addr, utils.DNSOptions{Domain: "blog.cloudflare.com", Qtype: dns.TypeHTTPS, RD: true}) + if err == nil { + if len(res.Answer) > 0 { + if _, ok := res.Answer[0].(*dns.HTTPS); ok { + result.HTTPSSupport = true + } + } + } else { + result.Err = err + return result + } + + return result +} + +func CombinedResultProcess(result_pool chan CombinedResult, wg *sync.WaitGroup) { + alive_cnt := 0 + edns_cnt := 0 + svcb_cnt := 0 + https_cnt := 0 + for { + if res, ok := <-result_pool; ok { + if res.Err != nil { + continue + } + if res.Alive { + alive_cnt++ + } + if res.EDNSSupport { + edns_cnt++ + } + if res.SVCBSupport { + svcb_cnt++ + } + if res.HTTPSSupport { + https_cnt++ + } + } else { + break + } + } + fmt.Printf("EDNS support Test: alive %v edns %v svcb %v https %v\n", alive_cnt, edns_cnt, svcb_cnt, https_cnt) + wg.Done() +} diff --git a/method/edns.go b/method/edns.go new file mode 100644 index 0000000..58efbdf --- /dev/null +++ b/method/edns.go @@ -0,0 +1,76 @@ +package method + +import ( + "edns_svcb/utils" + "fmt" + "log" + "sync" + + "github.com/miekg/dns" +) + +type TestResult struct { + Alive bool + Support bool + Err error +} + +func EDNSSupportMeasurement(ip_pool chan string, result_pool chan TestResult, routine_num int, wg *sync.WaitGroup, logger *log.Logger) { + for i := 0; i < routine_num; i++ { + wg.Add(1) + go EDNSSupportTestRoutine(ip_pool, result_pool, wg, logger) + } +} + +func EDNSSupportTestRoutine(ip_pool chan string, result_pool chan TestResult, wg *sync.WaitGroup, logger *log.Logger) { + for { + if ip, ok := <-ip_pool; ok { + addr := ip + ":53" + res := EDNSSupportTest(addr) + result_pool <- res + if res.Err != nil { + logger.Printf("%v : failed ( %v )", ip, res.Err) + } else { + logger.Printf("%v : alive %v support %v", ip, res.Alive, res.Support) + } + } else { + break + } + } + wg.Done() +} + +func EDNSSupportTest(addr string) TestResult { + _, err := utils.DNSQuery(addr, utils.DNSOptions{Domain: "www.example.net", RD: true}) + if err != nil { + return TestResult{Err: err} + } + res, err := utils.DNSQuery(addr, utils.DNSOptions{Domain: "www.example.net", EDNS: true, RD: true}) + if err == nil { + if len(res.Extra) == 1 { + if _, ok := res.Extra[0].(*dns.OPT); ok { + return TestResult{Alive: true, Support: true} + } + } + } + return TestResult{Alive: true} +} + +func EDNSSupportResultProcess(result_pool chan TestResult, wg *sync.WaitGroup) { + alive_cnt := 0 + valid_cnt := 0 + for { + if res, ok := <-result_pool; ok { + if res.Alive { + alive_cnt++ + } + if res.Support { + valid_cnt++ + } + } else { + break + } + } + fmt.Printf("EDNS support Test: alive %v support %v\n", alive_cnt, valid_cnt) + wg.Done() +} diff --git a/method/svcb.go b/method/svcb.go new file mode 100644 index 0000000..d208e00 --- /dev/null +++ b/method/svcb.go @@ -0,0 +1,63 @@ +package method + +import ( + "edns_svcb/utils" + + "github.com/miekg/dns" +) + +func HTTPSSupportTest(addr string) TestResult { + _, err := utils.DNSQuery(addr, utils.DNSOptions{Domain: "www.example.net", RD: true}) + if err != nil { + return TestResult{Err: err} + } + res, err := utils.DNSQuery(addr, utils.DNSOptions{Domain: "blog.cloudflare.com", Qtype: dns.TypeHTTPS, RD: true}) + if err == nil { + if len(res.Answer) == 1 { + if _, ok := res.Extra[0].(*dns.HTTPS); ok { + return TestResult{Alive: true, Support: true} + } + } + } + return TestResult{Alive: true} +} + +func SVCBSupportTest(addr string) TestResult { + _, err := utils.DNSQuery(addr, utils.DNSOptions{Domain: "www.example.net", RD: true}) + if err != nil { + return TestResult{Err: err} + } + res, err := utils.DNSQuery(addr, utils.DNSOptions{Domain: "blog.cloudflare.com", Qtype: dns.TypeSVCB, RD: true}) + if err == nil { + if len(res.Answer) == 1 { + if _, ok := res.Extra[0].(*dns.SVCB); ok { + return TestResult{Alive: true, Support: true} + } + } + } + return TestResult{Alive: true} +} + +func HTTPSRecordTest(addr, domain string) bool { + res, err := utils.DNSQuery(addr, utils.DNSOptions{Domain: domain, Qtype: dns.TypeHTTPS, RD: true}) + if err == nil { + if len(res.Answer) > 0 { + if _, ok := res.Answer[0].(*dns.HTTPS); ok { + return true + } + } + } + return false +} + +func SVCBRecordTest(addr, domain string) bool { + res, err := utils.DNSQuery(addr, utils.DNSOptions{Domain: domain, Qtype: dns.TypeSVCB, RD: true}) + if err == nil { + if len(res.Answer) > 0 { + if _, ok := res.Answer[0].(*dns.SVCB); ok { + return true + } + } + } + return false +} diff --git a/test/test-input b/test/test-input new file mode 100644 index 0000000..83d99ae --- /dev/null +++ b/test/test-input @@ -0,0 +1,100 @@ +1.0.0.1 +1.0.0.2 +1.0.0.3 +1.0.0.19 +1.0.66.192 +1.0.72.68 +1.0.77.169 +1.0.79.90 +1.0.81.135 +1.0.84.117 +1.0.91.59 +1.0.91.149 +1.0.97.194 +1.0.103.132 +1.0.106.125 +1.0.123.53 +1.0.141.2 +1.0.141.7 +1.0.141.23 +1.0.141.31 +1.0.141.32 +1.0.141.36 +1.0.141.46 +1.0.141.49 +1.0.141.54 +1.0.141.55 +1.0.141.59 +1.0.141.62 +1.0.141.64 +1.0.141.67 +1.0.141.69 +1.0.141.72 +1.0.141.75 +1.0.141.76 +1.0.141.77 +1.0.141.79 +1.0.141.85 +1.0.141.86 +1.0.141.92 +1.0.141.101 +1.0.141.103 +1.0.141.110 +1.0.141.114 +1.0.141.117 +1.0.141.126 +1.0.141.135 +1.0.141.143 +1.0.141.145 +1.0.141.152 +1.0.141.164 +1.0.141.167 +1.0.141.174 +1.0.141.177 +1.0.141.179 +1.0.141.180 +1.0.141.191 +1.0.141.203 +1.0.141.204 +1.0.141.211 +1.0.141.223 +1.0.141.238 +1.0.141.242 +1.0.141.244 +1.0.141.247 +1.0.141.250 +1.0.141.252 +1.0.141.253 +1.0.144.8 +1.0.144.27 +1.0.144.34 +1.0.144.36 +1.0.144.38 +1.0.144.47 +1.0.144.48 +1.0.144.50 +1.0.144.51 +1.0.144.52 +1.0.144.55 +1.0.144.73 +1.0.144.74 +1.0.144.78 +1.0.144.99 +1.0.144.112 +1.0.144.127 +1.0.144.130 +1.0.144.150 +1.0.144.164 +1.0.144.167 +1.0.144.172 +1.0.144.177 +1.0.144.178 +1.0.144.181 +1.0.144.182 +1.0.144.183 +1.0.144.186 +1.0.144.193 +1.0.144.197 +1.0.144.206 +1.0.144.207 +1.0.144.224 diff --git a/utils/dns_utils.go b/utils/dns_utils.go new file mode 100644 index 0000000..4a4131d --- /dev/null +++ b/utils/dns_utils.go @@ -0,0 +1,62 @@ +// dns utils +package utils + +import ( + "github.com/miekg/dns" +) + +type DNSOptions struct { + Domain string + RD bool + Qclass uint16 + Qtype uint16 + EDNS bool +} + +// 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(1232, false) + } + return msg +} + +// query and receive the response +// addr must contain dest port +// func DNSQuery(addr string, domain string, rd bool, qclass uint16, qtype uint16, edns bool) (*dns.Msg, error) { +func DNSQuery(addr string, opt DNSOptions) (*dns.Msg, error) { + if opt.Qclass == 0 { + opt.Qclass = 1 + } + if opt.Qtype == 0 { + opt.Qtype = 1 + } + msg := queryMaker(opt.Domain, opt.RD, opt.Qclass, opt.Qtype, opt.EDNS) + res, err := dns.Exchange(msg, addr) + return res, err +} + +func AsyncDNSQuery(addr string, domain string, rd bool, qclass uint16, qtype uint16, edns bool) error { + msg := queryMaker(domain, rd, qclass, qtype, edns) + client := dns.Client{Net: "udp"} + conn, err := client.Dial(addr) + if err != nil { + return err + } + defer conn.Close() + err = conn.WriteMsg(msg) + if err != nil { + return err + } + return nil +} diff --git a/utils/other_utils.go b/utils/other_utils.go new file mode 100644 index 0000000..d0b360e --- /dev/null +++ b/utils/other_utils.go @@ -0,0 +1,31 @@ +package utils + +import ( + "bufio" + _ "encoding/json" + _ "fmt" + "io" + _ "net" + _ "net/http" + "os" + _ "strconv" + "strings" +) + +func RetrieveLines(pool chan string, filename string) { + f, err := os.Open(filename) + if err != nil { + panic(err) + } + //fmt.Println("reading file ...") + reader := bufio.NewReader(f) + for { + s, err := reader.ReadString('\n') + if err == io.EOF { + break + } + s = strings.Trim(s, "\n") + pool <- s + } + close(pool) +}