commit 7f8ff7b6e4140d8a465e2da97c45fc8fa660b6f3 Author: Knut Ahlers Date: Sat May 25 17:09:31 2019 +0200 Initial version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7728244 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +rootzone diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2ce1cf5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019- Knut Ahlers + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..327bb42 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +[![Go Report Card](https://goreportcard.com/badge/github.com/Luzifer/rootzone)](https://goreportcard.com/report/github.com/Luzifer/rootzone) +![](https://badges.fyi/github/license/Luzifer/rootzone) +![](https://badges.fyi/github/downloads/Luzifer/rootzone) +![](https://badges.fyi/github/latest-release/Luzifer/rootzone) + +# Luzifer / rootzone + +`rootzone` is a small util for my [personal-dns](https://github.com/luzifer-docker/personal-dns) project to collect all IANA and OpenNIC TLDs and generate a named stub file for bind to be able to resolve those TLDs without delegation to third-party nameservers which might be modifying the original responses from the root nameservers. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b40fc5e --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/Luzifer/rootzone + +go 1.12 + +require ( + github.com/Luzifer/go_helpers/v2 v2.9.1 + github.com/Luzifer/rconfig/v2 v2.2.1 + github.com/miekg/dns v1.1.12 + github.com/pkg/errors v0.8.1 + github.com/sirupsen/logrus v1.4.2 + golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f // indirect + golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a2c633a --- /dev/null +++ b/go.sum @@ -0,0 +1,35 @@ +github.com/Luzifer/go_helpers/v2 v2.9.1 h1:MVUOlD6tJ2m/iTF0hllnI/QVZH5kI+TikUm1WRGg/c4= +github.com/Luzifer/go_helpers/v2 v2.9.1/go.mod h1:ZnWxPjyCdQ4rZP3kNiMSUW/7FigU1X9Rz8XopdJ5ZCU= +github.com/Luzifer/rconfig/v2 v2.2.1 h1:zcDdLQlnlzwcBJ8E0WFzOkQE1pCMn3EbX0dFYkeTczg= +github.com/Luzifer/rconfig/v2 v2.2.1/go.mod h1:OKIX0/JRZrPJ/ZXXWklQEFXA6tBfWaljZbW37w+sqBw= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/leekchan/gtf v0.0.0-20190214083521-5fba33c5b00b/go.mod h1:thNruaSwydMhkQ8dXzapABF9Sc1Tz08ZBcDdgott9RA= +github.com/miekg/dns v1.1.12 h1:WMhc1ik4LNkTg8U9l3hI1LvxKmIL+f1+WV/SZtCbDDA= +github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/validator.v2 v2.0.0-20180514200540-135c24b11c19 h1:WB265cn5OpO+hK3pikC9hpP1zI/KTwmyMFKloW9eOVc= +gopkg.in/validator.v2 v2.0.0-20180514200540-135c24b11c19/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/iana.go b/iana.go new file mode 100644 index 0000000..ddad1de --- /dev/null +++ b/iana.go @@ -0,0 +1,72 @@ +package main + +import ( + "bufio" + "net" + "net/http" + "strings" + + "github.com/miekg/dns" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" +) + +func getIANATLDs() ([]string, error) { + tlds := []string{} + + resp, err := http.Get(cfg.IANATldList) + if err != nil { + return nil, errors.Wrap(err, "Failed to retrieve IANA TLD list") + } + defer resp.Body.Close() + + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + if b := scanner.Bytes(); b[0] == '#' || len(b) == 0 { + continue + } + + tlds = append(tlds, strings.ToLower(scanner.Text())+".") + } + + return tlds, nil +} + +func getIANAZoneMasters(tld string) ([]string, error) { + log.WithField("tld", tld).Trace("Getting zone masters") + masters := []string{} + + c := new(dns.Client) + c.Net = "tcp" + + m := new(dns.Msg) + m.SetQuestion(tld, dns.TypeNS) + + r, _, err := c.Exchange(m, getRandomInternicRoot()) + if err != nil { + return nil, errors.Wrap(err, "Could not query nameservers") + } + + if r.Rcode != dns.RcodeSuccess { + return nil, errors.New("Query was not successful") + } + + for _, a := range r.Ns { + if ns, ok := a.(*dns.NS); ok { + var addr net.IP + // We can't resolve the root nameserver names so store glue records + for _, e := range r.Extra { + if nsa, ok := e.(*dns.A); ok && nsa.Header().Name == ns.Ns { + addr = nsa.A + break + } + } + + if addr != nil { + masters = append(masters, addr.String()) + } + } + } + + return masters, nil +} diff --git a/internic.go b/internic.go new file mode 100644 index 0000000..17811b3 --- /dev/null +++ b/internic.go @@ -0,0 +1,55 @@ +package main + +import ( + "bufio" + "math/rand" + "net/http" + "regexp" + "strings" + "sync" + "time" + + log "github.com/sirupsen/logrus" +) + +var ( + internicRoots []string + internicRootsFetch sync.Mutex +) + +func getRandomInternicRoot() string { + internicRootsFetch.Lock() + + rand.Seed(time.Now().UnixNano()) + if internicRoots != nil { + internicRootsFetch.Unlock() + return internicRoots[rand.Intn(len(internicRoots))] + } + + // Initialize InterNIC root cache + resp, err := http.Get(cfg.InternicRootFile) + if err != nil { + log.WithError(err).Fatal("Unable to get InterNIC root file") + } + defer resp.Body.Close() + + var ( + matcher = regexp.MustCompile(`\s+A\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$`) + roots = []string{} + scanner = bufio.NewScanner(resp.Body) + ) + + for scanner.Scan() { + m := matcher.FindStringSubmatch(scanner.Text()) + if len(m) != 2 { + continue + } + roots = append(roots, strings.Join([]string{m[1], "53"}, ":")) + } + + internicRoots = roots + + // Call self which triggers early-exit + internicRootsFetch.Unlock() + return getRandomInternicRoot() +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..e50af93 --- /dev/null +++ b/main.go @@ -0,0 +1,141 @@ +package main + +import ( + "fmt" + "os" + "sort" + "strings" + "sync" + "text/template" + + log "github.com/sirupsen/logrus" + + "github.com/Luzifer/go_helpers/v2/str" + "github.com/Luzifer/rconfig/v2" +) + +var ( + cfg = struct { + ConcurrencyLimit int `flag:"concurrency-limit" default:"50" description:"How many queries to execute in parallel"` + IANATldList string `flag:"iana-tld-list" vardefault:"iana-tld-list" description:"IANA TLD list file"` + IANAFilter []string `flag:"iana-filter" vardefault:"iana-filter" description:"IANA TLDs to igore"` + InternicRootFile string `flag:"internic-root-file" vardefault:"internic-root" description:"Internic root nameserver file"` + LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"` + OpenNICFilter []string `flag:"opennic-filter" vardefault:"opennic-filter" description:"OpenNIC TLDs to ignore"` + OpenNICRoot string `flag:"opennic-root" vardefault:"opennic-root" description:"OpenNIC root server"` + VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"` + }{} + + version = "dev" +) + +const stubTpl = `# Autogenerated with rootzone {{.version}} +#{{ range $tld, $ips := .roots }} +zone "{{ $tld }}" in { + type static-stub; + server-addresses { {{ range $ips }}{{ . }}; {{ end }}}; +}; +#{{ end }} + +# vim: set ft=named: +` + +func init() { + rconfig.SetVariableDefaults(map[string]string{ + "iana-filter": strings.Join([]string{"arpa."}, ","), + "iana-tld-list": "https://data.iana.org/TLD/tlds-alpha-by-domain.txt", + "internic-root": "https://www.internic.net/domain/named.root", + "opennic-filter": strings.Join([]string{".", "opennic.glue."}, ","), + "opennic-root": "75.127.96.89:53", + }) + if err := rconfig.ParseAndValidate(&cfg); err != nil { + log.Fatalf("Unable to parse commandline options: %s", err) + } + + if cfg.VersionAndExit { + fmt.Printf("rootzone %s\n", version) + os.Exit(0) + } + + if l, err := log.ParseLevel(cfg.LogLevel); err != nil { + log.WithError(err).Fatal("Unable to parse log level") + } else { + log.SetLevel(l) + } +} + +func main() { + var ( + cLimiter = make(chan struct{}, cfg.ConcurrencyLimit) + rootServersMutex = new(sync.Mutex) + wg = new(sync.WaitGroup) + ) + + // Initialize nameserver list before first request + getRandomInternicRoot() + + rootServers := map[string][]string{ + "opennic.glue.": {cfg.OpenNICRoot}, + } + + // Fetch IANA TLDs + ianaTLDs, err := getIANATLDs() + if err != nil { + log.WithError(err).Fatal("Unable to retrieve IANA TLDs") + } + setRootsFromTLDs(rootServers, rootServersMutex, ianaTLDs, cfg.IANAFilter, getIANAZoneMasters, wg, cLimiter) + + // Fetch OpenNIC TLDs + opennicTLDs, err := getOpenNICTLDs() + if err != nil { + log.WithError(err).Fatal("Unable to retrieve OpenNIC TLDs") + } + setRootsFromTLDs(rootServers, rootServersMutex, opennicTLDs, cfg.OpenNICFilter, getOpenNICZoneMasters, wg, cLimiter) + + wg.Wait() + + tpl, err := template.New("stub").Parse(stubTpl) + if err != nil { + log.WithError(err).Fatal("Unable to parse template") + } + + tpl.Execute(os.Stdout, map[string]interface{}{ + "roots": rootServers, + "version": version, + }) +} + +func setRootsFromTLDs(roots map[string][]string, rootsMutex *sync.Mutex, tlds []string, filter []string, resolver func(string) ([]string, error), wg *sync.WaitGroup, cLimiter chan struct{}) { + for _, tld := range tlds { + if !strings.HasSuffix(tld, ".") { + tld = tld + "." + } + + if str.StringInSlice(tld, filter) { + continue + } + + wg.Add(1) + cLimiter <- struct{}{} + + go func(tld string) { + defer func() { + wg.Done() + <-cLimiter + }() + + masters, err := resolver(tld) + if err != nil { + log.WithError(err).WithField("zone", tld).Error("Unable to retrieve zone masters") + return + } + + rootsMutex.Lock() + defer rootsMutex.Unlock() + + sort.Strings(masters) + + roots[tld] = masters + }(tld) + } +} diff --git a/opennic.go b/opennic.go new file mode 100644 index 0000000..acf8c26 --- /dev/null +++ b/opennic.go @@ -0,0 +1,85 @@ +package main + +import ( + "github.com/miekg/dns" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" +) + +func getOpenNICTLDs() ([]string, error) { + c := new(dns.Client) + c.Net = "tcp" + + m := new(dns.Msg) + m.SetQuestion("tlds.opennic.glue.", dns.TypeTXT) + + r, _, err := c.Exchange(m, cfg.OpenNICRoot) + if err != nil { + return nil, errors.Wrap(err, "Could not query nameservers") + } + + if r.Rcode != dns.RcodeSuccess { + return nil, errors.New("Query was not successful") + } + + tlds := []string{} + + for _, a := range r.Answer { + if txt, ok := a.(*dns.TXT); ok { + tlds = append(tlds, txt.Txt...) + } + } + + return tlds, nil +} + +func getOpenNICZoneMasters(tld string) ([]string, error) { + log.WithField("tld", tld).Trace("Getting zone masters") + + c := new(dns.Client) + c.Net = "tcp" + + m := new(dns.Msg) + m.SetQuestion(tld+"opennic.glue.", dns.TypeCNAME) + + r, _, err := c.Exchange(m, cfg.OpenNICRoot) + if err != nil { + return nil, errors.Wrap(err, "Could not query nameservers") + } + + if r.Rcode != dns.RcodeSuccess { + return nil, errors.Errorf("Query was not successful: %s", dns.RcodeToString[r.Rcode]) + } + + masters := []string{"ns0.opennic.glue."} + + for _, a := range r.Answer { + if cname, ok := a.(*dns.CNAME); ok { + masters = append(masters, cname.Target) + } + } + + masterIPs := []string{} + + for _, master := range masters { + m = new(dns.Msg) + m.SetQuestion(master, dns.TypeA) + + r, _, err := c.Exchange(m, cfg.OpenNICRoot) + if err != nil { + return nil, errors.Wrap(err, "Could not query nameservers") + } + + if r.Rcode != dns.RcodeSuccess { + return nil, errors.Errorf("Query was not successful: %s", dns.RcodeToString[r.Rcode]) + } + + for _, a := range r.Answer { + if rr, ok := a.(*dns.A); ok { + masterIPs = append(masterIPs, rr.A.String()) + } + } + } + + return masterIPs, nil +}