diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a605a3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test +.vscode/ +release/ + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +*.upx + +debug +dingo diff --git a/Docker_entrypoint.sh b/Docker_entrypoint.sh new file mode 100644 index 0000000..9915bcb --- /dev/null +++ b/Docker_entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +cmdArgs="$*" +if [ -n "$cmdArgs" ]; then + /opt/dingo $cmdArgs + exit 0 +fi + +Args=${Args:--gdns:auto -bind=0.0.0.0} + +cat > /opt/supervisord.conf < + +ARG DINGO_URL=https://github.com/chenhw2/dingo/releases/download/v20170417/dingo_linux-amd64-20170416.tar.gz + +RUN apk add --update --no-cache wget supervisor ca-certificates \ + && update-ca-certificates \ + && rm -rf /var/cache/apk/* + +RUN mkdir -p /opt \ + && cd /opt \ + && wget -qO- ${DINGO_URL} | tar xz \ + && mv dingo_* dingo + +ADD Docker_entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index f945500..7910f38 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,12 @@ Usage of dingo-linux-amd64: Google DNS: number of independent workers (default 10) -h1 use HTTPS/1.1 transport + -h1:proxy string + use Proxy of HTTP or SOCKS5, (Example "http://127.0.0.1:8080" or "socks(5)://127.0.0.1:1080") + -insecure + disable SSL Certificate check + -nocache + disable DNS Cache -odns:host string OpenDNS: HTTP 'Host' header (real FQDN, encrypted in TLS) (default "api.openresolve.com") -odns:server string diff --git a/build-release.sh b/build-release.sh new file mode 100755 index 0000000..3177f2e --- /dev/null +++ b/build-release.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +name='dingo' + +MD5='md5sum' +if [[ "$(uname)" == 'Darwin' ]]; then + MD5='md5' +fi + +UPX=false +if hash upx 2>/dev/null; then + UPX=true +fi + +VERSION=`date -u +%Y%m%d` +LDFLAGS="-X main.version=$VERSION -s -w -linkmode external -extldflags -static" +GCFLAGS="" + +# X86 +OSES=(windows linux darwin freebsd) +ARCHS=(amd64 386) +rm -rf ./release +mkdir -p ./release +for os in ${OSES[@]}; do + for arch in ${ARCHS[@]}; do + suffix="" + if [ "$os" == "windows" ]; then + suffix=".exe" + LDFLAGS="-X main.version=$VERSION -s -w" + fi + env CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o ./release/${name}_${os}_${arch}${suffix} . + if $UPX; then upx -9 ./release/${name}_${os}_${arch}${suffix}; fi + tar -C ./release -zcf ./release/${name}_${os}-${arch}-$VERSION.tar.gz ./${name}_${os}_${arch}${suffix} + $MD5 ./release/${name}_${os}-${arch}-$VERSION.tar.gz + done +done + +# ARM +ARMS=(5 6 7) +for v in ${ARMS[@]}; do + env CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=$v go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o ./release/${name}_arm$v . +done +if $UPX; then upx -9 ./release/${name}_arm*; fi +tar -C ./release -zcf ./release/${name}_arm-$VERSION.tar.gz $(for v in ${ARMS[@]}; do echo -n "./${name}_arm$v ";done) +$MD5 ./release/${name}_arm-$VERSION.tar.gz + +# MIPS # go 1.8+ required +LDFLAGS="-X main.version=$VERSION -s -w" +env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o ./release/${name}_mipsle . +env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o ./release/${name}_mips . + +if $UPX; then upx -9 ./release/${name}_mips**; fi +tar -C ./release -zcf ./release/${name}_mipsle-$VERSION.tar.gz ./${name}_mipsle +tar -C ./release -zcf ./release/${name}_mips-$VERSION.tar.gz ./${name}_mips +$MD5 ./release/${name}_mipsle-$VERSION.tar.gz diff --git a/build.sh b/build.sh deleted file mode 100755 index 0285a05..0000000 --- a/build.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -[ -z "$1" ] && { echo "Usage: build.sh VERSION" >&1; exit 1; } - -VERSION="$1" -DEST="$HOME/tmp/dingo-$VERSION" - -############################################### - -function build() -{ - TARGET="$1" - - echo "Building dingo v. $VERSION for $TARGET" - GOOS="${TARGET%-*}" GOARCH="${TARGET##*-}" go build \ - -o $DEST/dingo-$TARGET \ - ./*.go -} - -############################################### - -echo "Building in $DEST" -rm -fr $DEST -mkdir -p $DEST - -for target in \ - darwin-386 darwin-amd64 \ - freebsd-386 freebsd-amd64 \ - linux-386 linux-amd64 \ - netbsd-386 netbsd-amd64 \ - openbsd-386 openbsd-amd64 \ - windows-386 windows-amd64; do - build $target || exit 1 -done diff --git a/dingo.go b/dingo.go index 5a38c5c..7665508 100644 --- a/dingo.go +++ b/dingo.go @@ -9,48 +9,61 @@ package main -import "fmt" -import "os" -import "net" -import "flag" -import "log" -import "github.com/miekg/dns" -import "time" -import "github.com/patrickmn/go-cache" -import "math/rand" +import ( + "flag" + "fmt" + "log" + "math/rand" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/miekg/dns" + "github.com/patrickmn/go-cache" +) /**********************************************************************/ /* command-line arguments */ var ( - opt_bindip = flag.String("bind", "127.0.0.1", "IP address to bind to") - opt_port = flag.Int("port", 32000, "listen on port number") - opt_h1 = flag.Bool("h1", false, "use HTTPS/1.1 transport") - opt_quic = flag.Bool("quic", false, "use experimental QUIC transport") - opt_dbglvl = flag.Int("dbg", 2, "debugging level") + opt_bindip = flag.String("bind", "127.0.0.1", "IP address to bind to") + opt_port = flag.Int("port", 32000, "listen on port number") + opt_h1 = flag.Bool("h1", false, "use HTTPS/1.1 transport") + opt_proxy = flag.String("h1:proxy", "", "use Proxy of HTTP or SOCKS5, (Example \"http://127.0.0.1:8080\" or \"socks(5)://127.0.0.1:1080\")") + opt_quic = flag.Bool("quic", false, "use experimental QUIC transport") + opt_insecure = flag.Bool("insecure", false, "disable SSL Certificate check") + opt_nocache = flag.Bool("nocache", false, "disable DNS Cache") + opt_dbglvl = flag.Int("dbg", 2, "debugging level") ) /**********************************************************************/ /* logging stuff */ -func dbg(lvl int, fmt string, v ...interface{}) { if (*opt_dbglvl >= lvl) { dbglog.Printf(fmt, v...) } } +func dbg(lvl int, fmt string, v ...interface{}) { + if *opt_dbglvl >= lvl { + dbglog.Printf(fmt, v...) + } +} func die(msg error) { dbglog.Fatalln("fatal error:", msg.Error()) } + var dbglog *log.Logger /* structures */ type GRR struct { - Name string - Type uint16 - TTL uint32 - Data string + Name string + Type uint16 + TTL uint32 + Data string } type Reply struct { - Status int - TC bool - RD bool - RA bool - AD bool - CD bool + Status int + TC bool + RD bool + RA bool + AD bool + CD bool Question []GRR Answer []GRR Additional []GRR @@ -60,7 +73,12 @@ type Reply struct { } /* global channels */ -type Query struct { Name string; Type int; rchan *chan Reply } +type Query struct { + Name string + Type int + rchan *chan Reply +} + var qchan = make(chan Query, 100) /* global reply cache */ @@ -68,10 +86,12 @@ var rcache *cache.Cache /* module interface */ var Modules = make(map[string]Module) + type Module interface { Init() Start() } + func register(name string, mod Module) *Module { Modules[name] = mod return &mod @@ -79,44 +99,43 @@ func register(name string, mod Module) *Module { /**********************************************************************/ -/* UDP request handler */ -func handle(buf []byte, addr *net.UDPAddr, uc *net.UDPConn) { - /* try unpacking */ - msg := new(dns.Msg) - if err := msg.Unpack(buf); err != nil { - dbg(3, "unpack failed: %s", err) +/* DNS request handler */ +func handleDNS(w dns.ResponseWriter, req *dns.Msg) { + /* any questions? */ + if len(req.Question) < 1 { + dbg(3, "no questions") return - } else { - dbg(7, "unpacked message: %s", msg) } - /* any questions? */ - if (len(msg.Question) < 1) { dbg(3, "no questions"); return } - - qname := msg.Question[0].Name - qtype := msg.Question[0].Qtype + qname := req.Question[0].Name + qtype := req.Question[0].Qtype dbg(2, "resolving %s/%s", qname, dns.TypeToString[qtype]) /* check cache */ var r Reply cid := fmt.Sprintf("%s/%d", qname, qtype) if x, found := rcache.Get(cid); found { - // FIXME: update TTLs r = x.(Reply) } else { /* pass to resolvers and block until the response comes */ r = resolve(qname, int(qtype)) dbg(8, "got reply: %+v", r) - /* put to cache for 10 seconds (FIXME: use minimum TTL) */ - rcache.Set(cid, r, 10*time.Second) + if !(*opt_nocache) && len(r.Answer) > 0 { + ttl := r.Answer[0].TTL + if 0 < ttl && ttl < 30 { + ttl = 30 + } + /* put to cache for TTL(>=30) seconds */ + rcache.Set(cid, r, time.Duration(ttl)*time.Second) + } } /* rewrite the answers in r into rmsg */ rmsg := new(dns.Msg) - rmsg.SetReply(msg) + rmsg.SetReply(req) rmsg.Compress = true - if (r.Status >= 0) { + if r.Status >= 0 { rmsg.Rcode = r.Status rmsg.Truncated = r.TC rmsg.RecursionDesired = r.RD @@ -124,28 +143,33 @@ func handle(buf []byte, addr *net.UDPAddr, uc *net.UDPConn) { rmsg.AuthenticatedData = r.AD rmsg.CheckingDisabled = r.CD - for _,grr := range r.Answer { rmsg.Answer = append(rmsg.Answer, getrr(grr)) } - for _,grr := range r.Authority { rmsg.Ns = append(rmsg.Ns, getrr(grr)) } - for _,grr := range r.Additional { rmsg.Extra = append(rmsg.Extra, getrr(grr)) } + for _, grr := range r.Answer { + rmsg.Answer = append(rmsg.Answer, getrr(grr)) + } + for _, grr := range r.Authority { + rmsg.Ns = append(rmsg.Ns, getrr(grr)) + } + for _, grr := range r.Additional { + rmsg.Extra = append(rmsg.Extra, getrr(grr)) + } } else { rmsg.Rcode = 2 // SERVFAIL } dbg(8, "sending %s", rmsg.String()) -// rmsg.Truncated = true + // rmsg.Truncated = true - /* pack and send! */ - rbuf,err := rmsg.Pack() - if (err != nil) { dbg(2, "Pack() failed: %s", err); return } - uc.WriteToUDP(rbuf, addr) + w.WriteMsg(rmsg) } /* convert Google RR to miekg/dns RR */ func getrr(grr GRR) dns.RR { - hdr := dns.RR_Header{Name: grr.Name, Rrtype: grr.Type, Class: dns.ClassINET, Ttl: grr.TTL } + hdr := dns.RR_Header{Name: grr.Name, Rrtype: grr.Type, Class: dns.ClassINET, Ttl: grr.TTL} str := hdr.String() + grr.Data - rr,err := dns.NewRR(str) - if (err != nil) { dbg(3, "getrr(%s): %s", str, err.Error()) } + rr, err := dns.NewRR(str) + if err != nil { + dbg(3, "getrr(%s): %s", str, err.Error()) + } return rr } @@ -159,29 +183,45 @@ func resolve(name string, qtype int) Reply { /* main */ func main() { rand.Seed(time.Now().UnixNano()) - dbglog = log.New(os.Stderr, "", log.LstdFlags | log.LUTC) + dbglog = log.New(os.Stderr, "", log.LstdFlags|log.LUTC) /* prepare */ - for _,mod := range Modules { mod.Init() } + for _, mod := range Modules { + mod.Init() + } flag.Parse() rcache = cache.New(24*time.Hour, 60*time.Second) /* listen */ - laddr := net.UDPAddr{ IP: net.ParseIP(*opt_bindip), Port: *opt_port } - uc, err := net.ListenUDP("udp", &laddr) - if err != nil { die(err) } + laddr := net.UDPAddr{IP: net.ParseIP(*opt_bindip), Port: *opt_port} /* start workers */ - for _, mod := range Modules { mod.Start() } - - /* accept new connections forever */ - dbg(1, "dingo ver. 0.13 listening on %s UDP port %d", *opt_bindip, laddr.Port) - var buf []byte - for { - buf = make([]byte, 1500) - n, addr, err := uc.ReadFromUDP(buf) - if err == nil { go handle(buf[0:n], addr, uc) } + for _, mod := range Modules { + mod.Start() } - uc.Close() + /* accept new connections forever */ + dbg(1, "dingo ver. 0.13 listening on %s UDP+TCP port %d", laddr.IP, laddr.Port) + + udpServer := &dns.Server{Addr: fmt.Sprintf("%s:%d", laddr.IP, laddr.Port), Net: "udp"} + tcpServer := &dns.Server{Addr: fmt.Sprintf("%s:%d", laddr.IP, laddr.Port), Net: "tcp"} + dns.HandleFunc(".", handleDNS) + go func() { + if err := udpServer.ListenAndServe(); err != nil { + log.Fatal(err) + } + }() + go func() { + if err := tcpServer.ListenAndServe(); err != nil { + log.Fatal(err) + } + }() + + // Wait for SIGINT or SIGTERM + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + <-sigs + + udpServer.Shutdown() + tcpServer.Shutdown() } diff --git a/gdns.go b/gdns.go index 32d4d13..7a13f6e 100644 --- a/gdns.go +++ b/gdns.go @@ -18,36 +18,38 @@ import "flag" type Gdns struct { workers *int - server *string - auto *bool - sni *string - host *string - edns *string - nopad *bool + server *string + auto *bool + sni *string + host *string + edns *string + nopad *bool } /* command-line arguments */ func (r *Gdns) Init() { r.workers = flag.Int("gdns:workers", 10, "Google DNS: number of independent workers") - r.server = flag.String("gdns:server", "216.58.195.78", + r.server = flag.String("gdns:server", "216.58.195.78", "Google DNS: server address") - r.auto = flag.Bool("gdns:auto", false, + r.auto = flag.Bool("gdns:auto", false, "Google DNS: try to lookup the closest IPv4 server") - r.sni = flag.String("gdns:sni", "www.google.com", + r.sni = flag.String("gdns:sni", "www.google.com", "Google DNS: SNI string to send (should match server certificate)") - r.host = flag.String("gdns:host", "dns.google.com", + r.host = flag.String("gdns:host", "dns.google.com", "Google DNS: HTTP 'Host' header (real FQDN, encrypted in TLS)") - r.edns = flag.String("gdns:edns", "", + r.edns = flag.String("gdns:edns", "", "Google DNS: EDNS client subnet (set 0.0.0.0/0 to disable)") - r.nopad = flag.Bool("gdns:nopad", false, + r.nopad = flag.Bool("gdns:nopad", false, "Google DNS: disable random padding") } /**********************************************************************/ func (R *Gdns) Start() { - if *R.workers <= 0 { return } + if *R.workers <= 0 { + return + } if *R.auto { dbg(1, "resolving dns.google.com...") @@ -59,7 +61,9 @@ func (R *Gdns) Start() { dbg(1, "starting %d Google Public DNS client(s) querying server %s", *R.workers, *R.server) - for i := 0; i < *R.workers; i++ { go R.worker(*R.server) } + for i := 0; i < *R.workers; i++ { + go R.worker(*R.server) + } } func (R *Gdns) worker(server string) { @@ -70,7 +74,7 @@ func (R *Gdns) worker(server string) { } func (R *Gdns) resolve(https *Https, server string, qname string, qtype int) *Reply { - r := Reply{ Status: -1 } + r := Reply{Status: -1} v := url.Values{} /* prepare */ @@ -84,8 +88,10 @@ func (R *Gdns) resolve(https *Https, server string, qname string, qtype int) *Re } /* query */ - buf, err := https.Get(server, *R.host, "/resolve?" + v.Encode()) - if err != nil { return &r } + buf, err := https.Get(server, *R.host, "/resolve?"+v.Encode()) + if err != nil { + return &r + } /* parse */ r.Now = time.Now() diff --git a/https.go b/https.go index 1f0744b..61c6825 100644 --- a/https.go +++ b/https.go @@ -8,16 +8,22 @@ package main -import "time" -import "net/http" -import "io/ioutil" -import "crypto/tls" -import "errors" -import "golang.org/x/net/http2" -import "github.com/lucas-clemente/quic-go/h2quic" +import ( + "crypto/tls" + "errors" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "github.com/lucas-clemente/quic-go/h2quic" + "golang.org/x/net/http2" + "golang.org/x/net/proxy" +) type Https struct { - client http.Client + client http.Client } func NewHttps(sni string, forceh1 bool) *Https { @@ -26,23 +32,42 @@ func NewHttps(sni string, forceh1 bool) *Https { /* TLS setup */ tlscfg := new(tls.Config) tlscfg.ServerName = sni + tlscfg.InsecureSkipVerify = *opt_insecure /* HTTP transport */ var tr http.RoundTripper + forceh1 = forceh1 || len(*opt_proxy) > 0 // Force h1 to support proxy switch { case forceh1 || *opt_h1: - h1 := new(http.Transport) - h1.TLSClientConfig = tlscfg + h1 := &http.Transport{ + TLSClientConfig: tlscfg, + } + if proxyURL, err := url.Parse(*opt_proxy); err != nil { + dbg(1, "proxyURL = url.Parse(): %s", err) + } else { + switch strings.ToUpper(proxyURL.Scheme) { + case "HTTP": + h1.Proxy = func(_ *http.Request) (*url.URL, error) { + return proxyURL, nil + } + case "SOCKS5": + fallthrough + case "SOCKS": + dialer, _ := proxy.SOCKS5("tcp", proxyURL.Host, nil, proxy.Direct) + h1.Dial = dialer.Dial + } + } tr = h1 case *opt_quic: quic := new(h2quic.RoundTripper) -// quic.TLSClientConfig = tlscfg // FIXME + // quic.TLSClientConfig = tlscfg // FIXME tr = quic default: - h2 := new(http2.Transport) - h2.TLSClientConfig = tlscfg + h2 := &http2.Transport{ + TLSClientConfig: tlscfg, + } tr = h2 } diff --git a/odns.go b/odns.go index 0643868..84acae9 100644 --- a/odns.go +++ b/odns.go @@ -15,13 +15,13 @@ import "flag" import "github.com/miekg/dns" type OdnsReply struct { - ReturnCode string - ID int - AA bool - AD bool - RA bool - RD bool - TC bool + ReturnCode string + ID int + AA bool + AD bool + RA bool + RD bool + TC bool QuestionSection map[string]interface{} AnswerSection []map[string]interface{} AdditionalSection []map[string]interface{} @@ -32,9 +32,9 @@ type OdnsReply struct { type Odns struct { workers *int - server *string - sni *string - host *string + server *string + sni *string + host *string string2rcode map[string]int string2rtype map[string]uint16 @@ -51,23 +51,27 @@ func (R *Odns) Init() { "OpenDNS: HTTP 'Host' header (real FQDN, encrypted in TLS)") R.string2rcode = make(map[string]int) - for rcode,str := range dns.RcodeToString { + for rcode, str := range dns.RcodeToString { R.string2rcode[str] = rcode } R.string2rtype = make(map[string]uint16) - for rtype,str := range dns.TypeToString { + for rtype, str := range dns.TypeToString { R.string2rtype[str] = rtype } } /* start OpenDNS workers */ func (R *Odns) Start() { - if *R.workers <= 0 { return } + if *R.workers <= 0 { + return + } dbg(1, "starting %d OpenDNS client(s) querying server %s", *R.workers, *R.server) - for i := 0; i < *R.workers; i++ { go R.worker(*R.server) } + for i := 0; i < *R.workers; i++ { + go R.worker(*R.server) + } } /* handler of new requests */ @@ -80,14 +84,16 @@ func (R *Odns) worker(server string) { /* resolve single request */ func (R *Odns) resolve(https *Https, server string, qname string, qtype int) *Reply { - r := Reply{ Status: -1 } + r := Reply{Status: -1} /* prepare */ uri := fmt.Sprintf("/%s/%s", dns.Type(qtype).String(), qname) /* query */ buf, err := https.Get(server, *R.host, uri) - if err != nil { return &r } + if err != nil { + return &r + } r.Now = time.Now() /* parse */ @@ -102,34 +108,42 @@ func (R *Odns) resolve(https *Https, server string, qname string, qtype int) *Re r.AD = f.AD r.CD = false - for _,v := range f.AnswerSection { + for _, v := range f.AnswerSection { rr := R.odns2grr(v) - if rr != nil { r.Answer = append(r.Answer, *rr) } + if rr != nil { + r.Answer = append(r.Answer, *rr) + } } - for _,v := range f.AdditionalSection { + for _, v := range f.AdditionalSection { rr := R.odns2grr(v) - if rr != nil { r.Additional = append(r.Additional, *rr) } + if rr != nil { + r.Additional = append(r.Additional, *rr) + } } - for _,v := range f.AuthoritySection { + for _, v := range f.AuthoritySection { rr := R.odns2grr(v) - if rr != nil { r.Authority = append(r.Authority, *rr) } + if rr != nil { + r.Authority = append(r.Authority, *rr) + } } return &r } -func (R *Odns) odns2grr(v map[string]interface{}) (*GRR) { +func (R *Odns) odns2grr(v map[string]interface{}) *GRR { /* catch panics */ defer func() { - if r := recover(); r != nil { dbg(1, "panic in odns2grr()") } + if r := recover(); r != nil { + dbg(1, "panic in odns2grr()") + } }() /* get basic data */ - rname := v["Name"].(string) + rname := v["Name"].(string) rtypes := v["Type"].(string) - rttl := uint32(v["TTL"].(float64)) + rttl := uint32(v["TTL"].(float64)) /* parse type & data */ var rdata string @@ -146,7 +160,7 @@ func (R *Odns) odns2grr(v map[string]interface{}) (*GRR) { rdata = v["Target"].(string) case "MX": rtype = dns.TypeMX - mx := v["MailExchanger"].(string) + mx := v["MailExchanger"].(string) pref := v["Preference"].(float64) rdata = fmt.Sprintf("%d %s", int(pref), mx) case "NS": @@ -167,12 +181,12 @@ func (R *Odns) odns2grr(v map[string]interface{}) (*GRR) { rdata = v["Target"].(string) case "SOA": rtype = dns.TypeSOA - msn := v["MasterServerName"].(string) - mn := v["MaintainerName"].(string) - ser := v["Serial"].(float64) - ref := v["Refresh"].(float64) - ret := v["Retry"].(float64) - exp := v["Expire"].(float64) + msn := v["MasterServerName"].(string) + mn := v["MaintainerName"].(string) + ser := v["Serial"].(float64) + ref := v["Refresh"].(float64) + ret := v["Retry"].(float64) + exp := v["Expire"].(float64) nttl := v["NegativeTtl"].(float64) rdata = fmt.Sprintf("%s %s %d %d %d %d %d", msn, mn, int(ser), int(ref), int(ret), int(exp), int(nttl))