├── README.md ├── build.sh ├── dingo.go ├── gdns.go ├── https.go └── odns.go /README.md: -------------------------------------------------------------------------------- 1 | # dingo 2 | A DNS client (stub resolver) implemented in Go for the [Google 3 | DNS-over-HTTPS](https://developers.google.com/speed/public-dns/docs/dns-over-https). 4 | It effectively encrypts all your DNS traffic. It also supports 5 | [OpenResolve](https://www.openresolve.com/) by OpenDNS. 6 | 7 | The ultimate goal for the project is to provide a secure, caching DNS client that 8 | communicates with recursive DNS resolvers over encrypted channels only. For now, 9 | it resolves DNS queries over HTTP/2 in independent threads. The plans for 10 | future include better caching and support for QUIC. 11 | 12 | ## Quick start 13 | 14 | Download a pre-built binary for your platform from [the latest 15 | release](https://github.com/pforemski/dingo/releases/latest) (or build your own binaries). 16 | 17 | Run dingo as root on port 53. For example, on Linux: 18 | ``` 19 | $ sudo ./dingo-linux-amd64 -port=53 20 | ``` 21 | 22 | Update your DNS configuration. On Linux, edit your `/etc/resolv.conf` as root (remember to 23 | make backup first), e.g.: 24 | ``` 25 | $ sudo sh -c "echo nameserver 127.0.0.1 > /etc/resolv.conf" 26 | ``` 27 | 28 | ## Tuning dingo 29 | 30 | You will probably want to change the default Google DNS-over-HTTPS server IP address, using the 31 | `-gdns:server` option. First, resolve `dns.google.com` to IP address, which should give you the 32 | server closest to you: 33 | ``` 34 | $ host dns.google.com 35 | dns.google.com has address 216.58.209.174 36 | dns.google.com has IPv6 address 2a00:1450:401b:800::200e 37 | ``` 38 | 39 | Next, pass it to dingo. If you prefer IPv6, enclose the address in brackets, e.g.: 40 | ``` 41 | $ sudo ./dingo-linux-amd64 -port=53 -gdns:server=[2a00:1450:401b:800::200e] 42 | ``` 43 | 44 | To see all options, run `dingo -h`: 45 | ``` 46 | Usage of dingo-linux-amd64: 47 | -bind string 48 | IP address to bind to (default "127.0.0.1") 49 | -dbg int 50 | debugging level (default 2) 51 | -gdns:auto 52 | Google DNS: try to lookup the closest IPv4 server 53 | -gdns:edns string 54 | Google DNS: EDNS client subnet (set 0.0.0.0/0 to disable) 55 | -gdns:host string 56 | Google DNS: HTTP 'Host' header (real FQDN, encrypted in TLS) (default "dns.google.com") 57 | -gdns:nopad 58 | Google DNS: disable random padding 59 | -gdns:server string 60 | Google DNS: server address (default "216.58.195.78") 61 | -gdns:sni string 62 | Google DNS: SNI string to send (should match server certificate) (default "www.google.com") 63 | -gdns:workers int 64 | Google DNS: number of independent workers (default 10) 65 | -h1 66 | use HTTPS/1.1 transport 67 | -odns:host string 68 | OpenDNS: HTTP 'Host' header (real FQDN, encrypted in TLS) (default "api.openresolve.com") 69 | -odns:server string 70 | OpenDNS: web server address (default "67.215.70.81") 71 | -odns:sni string 72 | OpenDNS: TLS SNI string to send (unencrypted, must validate as server cert) (default "www.openresolve.com") 73 | -odns:workers int 74 | OpenDNS: number of independent workers 75 | -port int 76 | listen on port number (default 32000) 77 | 78 | ``` 79 | 80 | Finally, you will need to make dingo start in background each time you boot your machine. In Linux, 81 | you might want to use the [GNU Screen](https://en.wikipedia.org/wiki/GNU_Screen), which can start 82 | processes in background. For example, you might want to add the following line to your 83 | `/etc/rc.local`: 84 | ``` 85 | screen -dmS dingo /path/to/bin/dingo -port=53 -gdns:server=[2a00:1450:401b:800::200e] 86 | ``` 87 | 88 | ## Author 89 | 90 | Pawel Foremski, [pjf@foremski.pl](mailto:pjf@foremski.pl) 91 | 92 | Find me on: [LinkedIn](https://www.linkedin.com/in/pforemski), 93 | [Twitter](https://twitter.com/pforemski) 94 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | [ -z "$1" ] && { echo "Usage: build.sh VERSION" >&1; exit 1; } 4 | 5 | VERSION="$1" 6 | DEST="$HOME/tmp/dingo-$VERSION" 7 | 8 | ############################################### 9 | 10 | function build() 11 | { 12 | TARGET="$1" 13 | 14 | echo "Building dingo v. $VERSION for $TARGET" 15 | GOOS="${TARGET%-*}" GOARCH="${TARGET##*-}" go build \ 16 | -o $DEST/dingo-$TARGET \ 17 | ./*.go 18 | } 19 | 20 | ############################################### 21 | 22 | echo "Building in $DEST" 23 | rm -fr $DEST 24 | mkdir -p $DEST 25 | 26 | for target in \ 27 | darwin-386 darwin-amd64 \ 28 | freebsd-386 freebsd-amd64 \ 29 | linux-386 linux-amd64 \ 30 | netbsd-386 netbsd-amd64 \ 31 | openbsd-386 openbsd-amd64 \ 32 | windows-386 windows-amd64; do 33 | build $target || exit 1 34 | done 35 | -------------------------------------------------------------------------------- /dingo.go: -------------------------------------------------------------------------------- 1 | /** 2 | * dingo: a DNS caching proxy written in Go 3 | * 4 | * Copyright (C) 2016-2017 Pawel Foremski 5 | * Licensed under GNU GPL v3 6 | * 7 | * NOTE: this software is under development, far from being complete 8 | */ 9 | 10 | package main 11 | 12 | import "fmt" 13 | import "os" 14 | import "net" 15 | import "flag" 16 | import "log" 17 | import "github.com/miekg/dns" 18 | import "time" 19 | import "github.com/patrickmn/go-cache" 20 | import "math/rand" 21 | 22 | /**********************************************************************/ 23 | 24 | /* command-line arguments */ 25 | var ( 26 | opt_bindip = flag.String("bind", "127.0.0.1", "IP address to bind to") 27 | opt_port = flag.Int("port", 32000, "listen on port number") 28 | opt_h1 = flag.Bool("h1", false, "use HTTPS/1.1 transport") 29 | opt_quic = flag.Bool("quic", false, "use experimental QUIC transport") 30 | opt_dbglvl = flag.Int("dbg", 2, "debugging level") 31 | ) 32 | 33 | /**********************************************************************/ 34 | 35 | /* logging stuff */ 36 | func dbg(lvl int, fmt string, v ...interface{}) { if (*opt_dbglvl >= lvl) { dbglog.Printf(fmt, v...) } } 37 | func die(msg error) { dbglog.Fatalln("fatal error:", msg.Error()) } 38 | var dbglog *log.Logger 39 | 40 | /* structures */ 41 | type GRR struct { 42 | Name string 43 | Type uint16 44 | TTL uint32 45 | Data string 46 | } 47 | type Reply struct { 48 | Status int 49 | TC bool 50 | RD bool 51 | RA bool 52 | AD bool 53 | CD bool 54 | Question []GRR 55 | Answer []GRR 56 | Additional []GRR 57 | Authority []GRR 58 | Comment string 59 | Now time.Time 60 | } 61 | 62 | /* global channels */ 63 | type Query struct { Name string; Type int; rchan *chan Reply } 64 | var qchan = make(chan Query, 100) 65 | 66 | /* global reply cache */ 67 | var rcache *cache.Cache 68 | 69 | /* module interface */ 70 | var Modules = make(map[string]Module) 71 | type Module interface { 72 | Init() 73 | Start() 74 | } 75 | func register(name string, mod Module) *Module { 76 | Modules[name] = mod 77 | return &mod 78 | } 79 | 80 | /**********************************************************************/ 81 | 82 | /* UDP request handler */ 83 | func handle(buf []byte, addr *net.UDPAddr, uc *net.UDPConn) { 84 | /* try unpacking */ 85 | msg := new(dns.Msg) 86 | if err := msg.Unpack(buf); err != nil { 87 | dbg(3, "unpack failed: %s", err) 88 | return 89 | } else { 90 | dbg(7, "unpacked message: %s", msg) 91 | } 92 | 93 | /* any questions? */ 94 | if (len(msg.Question) < 1) { dbg(3, "no questions"); return } 95 | 96 | qname := msg.Question[0].Name 97 | qtype := msg.Question[0].Qtype 98 | dbg(2, "resolving %s/%s", qname, dns.TypeToString[qtype]) 99 | 100 | /* check cache */ 101 | var r Reply 102 | cid := fmt.Sprintf("%s/%d", qname, qtype) 103 | if x, found := rcache.Get(cid); found { 104 | // FIXME: update TTLs 105 | r = x.(Reply) 106 | } else { 107 | /* pass to resolvers and block until the response comes */ 108 | r = resolve(qname, int(qtype)) 109 | dbg(8, "got reply: %+v", r) 110 | 111 | /* put to cache for 10 seconds (FIXME: use minimum TTL) */ 112 | rcache.Set(cid, r, 10*time.Second) 113 | } 114 | 115 | /* rewrite the answers in r into rmsg */ 116 | rmsg := new(dns.Msg) 117 | rmsg.SetReply(msg) 118 | rmsg.Compress = true 119 | if (r.Status >= 0) { 120 | rmsg.Rcode = r.Status 121 | rmsg.Truncated = r.TC 122 | rmsg.RecursionDesired = r.RD 123 | rmsg.RecursionAvailable = r.RA 124 | rmsg.AuthenticatedData = r.AD 125 | rmsg.CheckingDisabled = r.CD 126 | 127 | for _,grr := range r.Answer { rmsg.Answer = append(rmsg.Answer, getrr(grr)) } 128 | for _,grr := range r.Authority { rmsg.Ns = append(rmsg.Ns, getrr(grr)) } 129 | for _,grr := range r.Additional { rmsg.Extra = append(rmsg.Extra, getrr(grr)) } 130 | } else { 131 | rmsg.Rcode = 2 // SERVFAIL 132 | } 133 | 134 | dbg(8, "sending %s", rmsg.String()) 135 | // rmsg.Truncated = true 136 | 137 | /* pack and send! */ 138 | rbuf,err := rmsg.Pack() 139 | if (err != nil) { dbg(2, "Pack() failed: %s", err); return } 140 | uc.WriteToUDP(rbuf, addr) 141 | } 142 | 143 | /* convert Google RR to miekg/dns RR */ 144 | func getrr(grr GRR) dns.RR { 145 | hdr := dns.RR_Header{Name: grr.Name, Rrtype: grr.Type, Class: dns.ClassINET, Ttl: grr.TTL } 146 | str := hdr.String() + grr.Data 147 | rr,err := dns.NewRR(str) 148 | if (err != nil) { dbg(3, "getrr(%s): %s", str, err.Error()) } 149 | return rr 150 | } 151 | 152 | /* pass to the request queue and wait until reply */ 153 | func resolve(name string, qtype int) Reply { 154 | rchan := make(chan Reply, 1) 155 | qchan <- Query{name, qtype, &rchan} 156 | return <-rchan 157 | } 158 | 159 | /* main */ 160 | func main() { 161 | rand.Seed(time.Now().UnixNano()) 162 | dbglog = log.New(os.Stderr, "", log.LstdFlags | log.LUTC) 163 | 164 | /* prepare */ 165 | for _,mod := range Modules { mod.Init() } 166 | flag.Parse() 167 | rcache = cache.New(24*time.Hour, 60*time.Second) 168 | 169 | /* listen */ 170 | laddr := net.UDPAddr{ IP: net.ParseIP(*opt_bindip), Port: *opt_port } 171 | uc, err := net.ListenUDP("udp", &laddr) 172 | if err != nil { die(err) } 173 | 174 | /* start workers */ 175 | for _, mod := range Modules { mod.Start() } 176 | 177 | /* accept new connections forever */ 178 | dbg(1, "dingo ver. 0.13 listening on %s UDP port %d", *opt_bindip, laddr.Port) 179 | var buf []byte 180 | for { 181 | buf = make([]byte, 1500) 182 | n, addr, err := uc.ReadFromUDP(buf) 183 | if err == nil { go handle(buf[0:n], addr, uc) } 184 | } 185 | 186 | uc.Close() 187 | } 188 | -------------------------------------------------------------------------------- /gdns.go: -------------------------------------------------------------------------------- 1 | /** 2 | * dingo: a DNS caching proxy written in Go 3 | * This file implements a Google DNS-over-HTTPS client 4 | * 5 | * Copyright (C) 2016 Pawel Foremski 6 | * Licensed under GNU GPL v3 7 | */ 8 | 9 | package main 10 | 11 | import "fmt" 12 | import "net/url" 13 | import "time" 14 | import "encoding/json" 15 | import "math/rand" 16 | import "strings" 17 | import "flag" 18 | 19 | type Gdns struct { 20 | workers *int 21 | server *string 22 | auto *bool 23 | sni *string 24 | host *string 25 | edns *string 26 | nopad *bool 27 | } 28 | 29 | /* command-line arguments */ 30 | func (r *Gdns) Init() { 31 | r.workers = flag.Int("gdns:workers", 10, 32 | "Google DNS: number of independent workers") 33 | r.server = flag.String("gdns:server", "216.58.195.78", 34 | "Google DNS: server address") 35 | r.auto = flag.Bool("gdns:auto", false, 36 | "Google DNS: try to lookup the closest IPv4 server") 37 | r.sni = flag.String("gdns:sni", "www.google.com", 38 | "Google DNS: SNI string to send (should match server certificate)") 39 | r.host = flag.String("gdns:host", "dns.google.com", 40 | "Google DNS: HTTP 'Host' header (real FQDN, encrypted in TLS)") 41 | r.edns = flag.String("gdns:edns", "", 42 | "Google DNS: EDNS client subnet (set 0.0.0.0/0 to disable)") 43 | r.nopad = flag.Bool("gdns:nopad", false, 44 | "Google DNS: disable random padding") 45 | } 46 | 47 | /**********************************************************************/ 48 | 49 | func (R *Gdns) Start() { 50 | if *R.workers <= 0 { return } 51 | 52 | if *R.auto { 53 | dbg(1, "resolving dns.google.com...") 54 | r4 := R.resolve(NewHttps(*R.sni, false), *R.server, "dns.google.com", 1) 55 | if r4.Status == 0 && len(r4.Answer) > 0 { 56 | R.server = &r4.Answer[0].Data 57 | } 58 | } 59 | 60 | dbg(1, "starting %d Google Public DNS client(s) querying server %s", 61 | *R.workers, *R.server) 62 | for i := 0; i < *R.workers; i++ { go R.worker(*R.server) } 63 | } 64 | 65 | func (R *Gdns) worker(server string) { 66 | var https = NewHttps(*R.sni, false) 67 | for q := range qchan { 68 | *q.rchan <- *R.resolve(https, server, q.Name, q.Type) 69 | } 70 | } 71 | 72 | func (R *Gdns) resolve(https *Https, server string, qname string, qtype int) *Reply { 73 | r := Reply{ Status: -1 } 74 | v := url.Values{} 75 | 76 | /* prepare */ 77 | v.Set("name", qname) 78 | v.Set("type", fmt.Sprintf("%d", qtype)) 79 | if len(*R.edns) > 0 { 80 | v.Set("edns_client_subnet", *R.edns) 81 | } 82 | if !*R.nopad { 83 | v.Set("random_padding", strings.Repeat(string(65+rand.Intn(26)), rand.Intn(500))) 84 | } 85 | 86 | /* query */ 87 | buf, err := https.Get(server, *R.host, "/resolve?" + v.Encode()) 88 | if err != nil { return &r } 89 | 90 | /* parse */ 91 | r.Now = time.Now() 92 | json.Unmarshal(buf, &r) 93 | 94 | return &r 95 | } 96 | 97 | /* register module */ 98 | var _ = register("gdns", new(Gdns)) 99 | -------------------------------------------------------------------------------- /https.go: -------------------------------------------------------------------------------- 1 | /** 2 | * dingo: a DNS caching proxy written in Go 3 | * This file implements common code for HTTPS+JSON requests 4 | * 5 | * Copyright (C) 2016-2017 Pawel Foremski 6 | * Licensed under GNU GPL v3 7 | */ 8 | 9 | package main 10 | 11 | import "time" 12 | import "net/http" 13 | import "io/ioutil" 14 | import "crypto/tls" 15 | import "errors" 16 | import "golang.org/x/net/http2" 17 | import "github.com/lucas-clemente/quic-go/h2quic" 18 | 19 | type Https struct { 20 | client http.Client 21 | } 22 | 23 | func NewHttps(sni string, forceh1 bool) *Https { 24 | H := Https{} 25 | 26 | /* TLS setup */ 27 | tlscfg := new(tls.Config) 28 | tlscfg.ServerName = sni 29 | 30 | /* HTTP transport */ 31 | var tr http.RoundTripper 32 | switch { 33 | case forceh1 || *opt_h1: 34 | h1 := new(http.Transport) 35 | h1.TLSClientConfig = tlscfg 36 | tr = h1 37 | 38 | case *opt_quic: 39 | quic := new(h2quic.RoundTripper) 40 | // quic.TLSClientConfig = tlscfg // FIXME 41 | tr = quic 42 | 43 | default: 44 | h2 := new(http2.Transport) 45 | h2.TLSClientConfig = tlscfg 46 | tr = h2 47 | } 48 | 49 | /* HTTP client */ 50 | H.client.Timeout = time.Second * 10 51 | H.client.Transport = tr 52 | 53 | return &H 54 | } 55 | 56 | func (R *Https) Get(ip string, host string, uri string) ([]byte, error) { 57 | url := "https://" + ip + uri 58 | hreq, err := http.NewRequest("GET", url, nil) 59 | if err != nil { 60 | dbg(1, "http.NewRequest(): %s", err) 61 | return nil, err 62 | } 63 | hreq.Host = host // FIXME: doesn't have an effect for QUIC 64 | 65 | /* send the query */ 66 | resp, err := R.client.Do(hreq) 67 | if err != nil { 68 | dbg(1, "http.Do(): %s", err) 69 | return nil, err 70 | } 71 | dbg(3, "http.Do(%s): %s %s", url, resp.Status, resp.Proto) 72 | 73 | /* read */ 74 | buf, err := ioutil.ReadAll(resp.Body) 75 | resp.Body.Close() 76 | if err != nil { 77 | dbg(1, "ioutil.ReadAll(%s): %s", url, err) 78 | return nil, err 79 | } 80 | dbg(7, " reply: %s", buf) 81 | 82 | /* HTTP 200 OK? */ 83 | if resp.StatusCode != 200 { 84 | dbg(1, "resp.StatusCode != 200: %s", url) 85 | return nil, errors.New("response code != 200") 86 | } 87 | 88 | return buf, nil 89 | } 90 | -------------------------------------------------------------------------------- /odns.go: -------------------------------------------------------------------------------- 1 | /** 2 | * dingo: a DNS caching proxy written in Go 3 | * This file implements an OpenDNS www.openresolve.com client 4 | * 5 | * Copyright (C) 2016 Pawel Foremski 6 | * Licensed under GNU GPL v3 7 | */ 8 | 9 | package main 10 | 11 | import "fmt" 12 | import "encoding/json" 13 | import "time" 14 | import "flag" 15 | import "github.com/miekg/dns" 16 | 17 | type OdnsReply struct { 18 | ReturnCode string 19 | ID int 20 | AA bool 21 | AD bool 22 | RA bool 23 | RD bool 24 | TC bool 25 | QuestionSection map[string]interface{} 26 | AnswerSection []map[string]interface{} 27 | AdditionalSection []map[string]interface{} 28 | AuthoritySection []map[string]interface{} 29 | } 30 | 31 | /***********************************************************/ 32 | 33 | type Odns struct { 34 | workers *int 35 | server *string 36 | sni *string 37 | host *string 38 | 39 | string2rcode map[string]int 40 | string2rtype map[string]uint16 41 | } 42 | 43 | func (R *Odns) Init() { 44 | R.workers = flag.Int("odns:workers", 0, 45 | "OpenDNS: number of independent workers") 46 | R.server = flag.String("odns:server", "67.215.70.81", 47 | "OpenDNS: web server address") 48 | R.sni = flag.String("odns:sni", "www.openresolve.com", 49 | "OpenDNS: TLS SNI string to send (unencrypted, must validate as server cert)") 50 | R.host = flag.String("odns:host", "api.openresolve.com", 51 | "OpenDNS: HTTP 'Host' header (real FQDN, encrypted in TLS)") 52 | 53 | R.string2rcode = make(map[string]int) 54 | for rcode,str := range dns.RcodeToString { 55 | R.string2rcode[str] = rcode 56 | } 57 | 58 | R.string2rtype = make(map[string]uint16) 59 | for rtype,str := range dns.TypeToString { 60 | R.string2rtype[str] = rtype 61 | } 62 | } 63 | 64 | /* start OpenDNS workers */ 65 | func (R *Odns) Start() { 66 | if *R.workers <= 0 { return } 67 | 68 | dbg(1, "starting %d OpenDNS client(s) querying server %s", 69 | *R.workers, *R.server) 70 | for i := 0; i < *R.workers; i++ { go R.worker(*R.server) } 71 | } 72 | 73 | /* handler of new requests */ 74 | func (R *Odns) worker(server string) { 75 | var https = NewHttps(*R.sni, true) 76 | for q := range qchan { 77 | *q.rchan <- *R.resolve(https, server, q.Name, q.Type) 78 | } 79 | } 80 | 81 | /* resolve single request */ 82 | func (R *Odns) resolve(https *Https, server string, qname string, qtype int) *Reply { 83 | r := Reply{ Status: -1 } 84 | 85 | /* prepare */ 86 | uri := fmt.Sprintf("/%s/%s", dns.Type(qtype).String(), qname) 87 | 88 | /* query */ 89 | buf, err := https.Get(server, *R.host, uri) 90 | if err != nil { return &r } 91 | r.Now = time.Now() 92 | 93 | /* parse */ 94 | var f OdnsReply 95 | json.Unmarshal(buf, &f) 96 | 97 | /* rewrite */ 98 | r.Status = R.string2rcode[f.ReturnCode] 99 | r.TC = f.TC 100 | r.RD = f.RD 101 | r.RA = f.RA 102 | r.AD = f.AD 103 | r.CD = false 104 | 105 | for _,v := range f.AnswerSection { 106 | rr := R.odns2grr(v) 107 | if rr != nil { r.Answer = append(r.Answer, *rr) } 108 | } 109 | 110 | for _,v := range f.AdditionalSection { 111 | rr := R.odns2grr(v) 112 | if rr != nil { r.Additional = append(r.Additional, *rr) } 113 | } 114 | 115 | for _,v := range f.AuthoritySection { 116 | rr := R.odns2grr(v) 117 | if rr != nil { r.Authority = append(r.Authority, *rr) } 118 | } 119 | 120 | return &r 121 | } 122 | 123 | func (R *Odns) odns2grr(v map[string]interface{}) (*GRR) { 124 | /* catch panics */ 125 | defer func() { 126 | if r := recover(); r != nil { dbg(1, "panic in odns2grr()") } 127 | }() 128 | 129 | /* get basic data */ 130 | rname := v["Name"].(string) 131 | rtypes := v["Type"].(string) 132 | rttl := uint32(v["TTL"].(float64)) 133 | 134 | /* parse type & data */ 135 | var rdata string 136 | var rtype uint16 137 | switch rtypes { 138 | case "A": 139 | rtype = dns.TypeA 140 | rdata = v["Address"].(string) 141 | case "AAAA": 142 | rtype = dns.TypeAAAA 143 | rdata = v["Address"].(string) 144 | case "CNAME": 145 | rtype = dns.TypeCNAME 146 | rdata = v["Target"].(string) 147 | case "MX": 148 | rtype = dns.TypeMX 149 | mx := v["MailExchanger"].(string) 150 | pref := v["Preference"].(float64) 151 | rdata = fmt.Sprintf("%d %s", int(pref), mx) 152 | case "NS": 153 | rtype = dns.TypeNS 154 | rdata = v["Target"].(string) 155 | case "NAPTR": 156 | rtype = dns.TypeNAPTR 157 | flg := v["Flags"].(string) 158 | ord := v["Order"].(float64) 159 | svc := v["Service"].(string) 160 | prf := v["Preference"].(float64) 161 | reg := v["Regexp"].(string) 162 | rep := v["Replacement"].(string) 163 | rdata = fmt.Sprintf("%d %d \"%s\" \"%s\" \"%s\" %s", 164 | int(ord), int(prf), flg, svc, reg, rep) 165 | case "PTR": 166 | rtype = dns.TypePTR 167 | rdata = v["Target"].(string) 168 | case "SOA": 169 | rtype = dns.TypeSOA 170 | msn := v["MasterServerName"].(string) 171 | mn := v["MaintainerName"].(string) 172 | ser := v["Serial"].(float64) 173 | ref := v["Refresh"].(float64) 174 | ret := v["Retry"].(float64) 175 | exp := v["Expire"].(float64) 176 | nttl := v["NegativeTtl"].(float64) 177 | rdata = fmt.Sprintf("%s %s %d %d %d %d %d", 178 | msn, mn, int(ser), int(ref), int(ret), int(exp), int(nttl)) 179 | case "TXT": 180 | rtype = dns.TypeTXT 181 | rdata = v["TxtData"].(string) 182 | default: 183 | dbg(1, "odns2grr(): %s unsupported", rtypes) 184 | return nil 185 | } 186 | 187 | return &GRR{rname, rtype, rttl, rdata} 188 | } 189 | 190 | /* register module */ 191 | var _ = register("odns", new(Odns)) 192 | --------------------------------------------------------------------------------