func statusRR(label string) []dns.RR { h := dns.RR_Header{Ttl: 1, Class: dns.ClassINET, Rrtype: dns.TypeTXT} h.Name = label status := map[string]string{"v": VERSION, "id": serverID} hostname, err := os.Hostname() if err == nil { status["h"] = hostname } status["up"] = strconv.Itoa(int(time.Since(timeStarted).Seconds())) status["qs"] = strconv.FormatInt(qCounter.Count(), 10) status["qps1"] = fmt.Sprintf("%.4f", qCounter.Rate1()) js, err := json.Marshal(status) return []dns.RR{&dns.TXT{Hdr: h, Txt: []string{string(js)}}} }
func setupZoneData(data map[string]interface{}, Zone *Zone) { recordTypes := map[string]uint16{ "a": dns.TypeA, "aaaa": dns.TypeAAAA, "alias": dns.TypeMF, "cname": dns.TypeCNAME, "mx": dns.TypeMX, "ns": dns.TypeNS, "txt": dns.TypeTXT, } for dk, dv_inter := range data { dv := dv_inter.(map[string]interface{}) //log.Printf("K %s V %s TYPE-V %T\n", dk, dv, dv) label := Zone.AddLabel(dk) for rType, rdata := range dv { switch rType { case "max_hosts": label.MaxHosts = valueToInt(rdata) continue case "ttl": label.Ttl = valueToInt(rdata) continue } dnsType, ok := recordTypes[rType] if !ok { log.Printf("Unsupported record type '%s'\n", rType) continue } if rdata == nil { //log.Printf("No %s records for label %s\n", rType, dk) continue } //log.Printf("rdata %s TYPE-R %T\n", rdata, rdata) records := make(map[string][]interface{}) switch rdata.(type) { case map[string]interface{}: // Handle NS map syntax, map[ns2.example.net:<nil> ns1.example.net:<nil>] tmp := make([]interface{}, 0) for rdataK, rdataV := range rdata.(map[string]interface{}) { if rdataV == nil { rdataV = "" } tmp = append(tmp, []string{rdataK, rdataV.(string)}) } records[rType] = tmp case string: // CNAME and alias tmp := make([]interface{}, 1) tmp[0] = rdata.(string) records[rType] = tmp default: records[rType] = rdata.([]interface{}) } //log.Printf("RECORDS %s TYPE-REC %T\n", Records, Records) label.Records[dnsType] = make(Records, len(records[rType])) for i := 0; i < len(records[rType]); i++ { //log.Printf("RT %T %#v\n", records[rType][i], records[rType][i]) record := new(Record) var h dns.RR_Header // log.Println("TTL OPTIONS", Zone.Options.Ttl) h.Ttl = uint32(label.Ttl) h.Class = dns.ClassINET h.Rrtype = dnsType h.Name = label.Label + "." + Zone.Origin + "." switch dnsType { case dns.TypeA, dns.TypeAAAA: rec := records[rType][i].([]interface{}) ip := rec[0].(string) var err error if len(rec) > 1 { switch rec[1].(type) { case string: record.Weight, err = strconv.Atoi(rec[1].(string)) if err != nil { panic("Error converting weight to integer") } case float64: record.Weight = int(rec[1].(float64)) } } switch dnsType { case dns.TypeA: if x := net.ParseIP(ip); x != nil { record.RR = &dns.A{Hdr: h, A: x} break } panic(fmt.Errorf("Bad A record %s for %s", ip, dk)) case dns.TypeAAAA: if x := net.ParseIP(ip); x != nil { record.RR = &dns.AAAA{Hdr: h, AAAA: x} break } panic(fmt.Errorf("Bad AAAA record %s for %s", ip, dk)) } case dns.TypeMX: rec := records[rType][i].(map[string]interface{}) pref := uint16(0) mx := rec["mx"].(string) if !strings.HasSuffix(mx, ".") { mx = mx + "." } if rec["weight"] != nil { record.Weight = valueToInt(rec["weight"]) } if rec["preference"] != nil { pref = uint16(valueToInt(rec["preference"])) } record.RR = &dns.MX{ Hdr: h, Mx: mx, Preference: pref} case dns.TypeCNAME: rec := records[rType][i] target := rec.(string) if !dns.IsFqdn(target) { target = target + "." + Zone.Origin } record.RR = &dns.CNAME{Hdr: h, Target: dns.Fqdn(target)} case dns.TypeMF: rec := records[rType][i] // MF records (how we store aliases) are not FQDNs record.RR = &dns.MF{Hdr: h, Mf: rec.(string)} case dns.TypeNS: rec := records[rType][i] if h.Ttl < 86400 { h.Ttl = 86400 } var ns string switch rec.(type) { case string: ns = rec.(string) case []string: recl := rec.([]string) ns = recl[0] if len(recl[1]) > 0 { log.Println("NS records with names syntax not supported") } default: log.Printf("Data: %T %#v\n", rec, rec) panic("Unrecognized NS format/syntax") } rr := &dns.NS{Hdr: h, Ns: dns.Fqdn(ns)} record.RR = rr case dns.TypeTXT: rec := records[rType][i] var txt string switch rec.(type) { case string: txt = rec.(string) case map[string]interface{}: recmap := rec.(map[string]interface{}) if weight, ok := recmap["weight"]; ok { record.Weight = valueToInt(weight) } if t, ok := recmap["txt"]; ok { txt = t.(string) } } if len(txt) > 0 { rr := &dns.TXT{Hdr: h, Txt: []string{txt}} record.RR = rr } else { log.Printf("Zero length txt record for '%s' in '%s'\n", label.Label, Zone.Origin) continue } default: log.Println("type:", rType) panic("Don't know how to handle this type") } if record.RR == nil { panic("record.RR is nil") } label.Weight[dnsType] += record.Weight label.Records[dnsType][i] = *record } if label.Weight[dnsType] > 0 { sort.Sort(RecordsByWeight{label.Records[dnsType]}) } } } setupSOA(Zone) //log.Println(Zones[k]) }
func setupZoneData(data map[string]interface{}, Zone *Zone) { recordTypes := map[string]uint16{ "a": dns.TypeA, "aaaa": dns.TypeAAAA, "alias": dns.TypeMF, "cname": dns.TypeCNAME, "mx": dns.TypeMX, "ns": dns.TypeNS, "txt": dns.TypeTXT, "spf": dns.TypeSPF, "srv": dns.TypeSRV, } for dk, dv_inter := range data { dv := dv_inter.(map[string]interface{}) //log.Printf("K %s V %s TYPE-V %T\n", dk, dv, dv) label := Zone.AddLabel(dk) for rType, rdata := range dv { switch rType { case "max_hosts": label.MaxHosts = valueToInt(rdata) continue case "ttl": label.Ttl = valueToInt(rdata) continue } dnsType, ok := recordTypes[rType] if !ok { log.Printf("Unsupported record type '%s'\n", rType) continue } if rdata == nil { //log.Printf("No %s records for label %s\n", rType, dk) continue } //log.Printf("rdata %s TYPE-R %T\n", rdata, rdata) records := make(map[string][]interface{}) switch rdata.(type) { case map[string]interface{}: // Handle NS map syntax, map[ns2.example.net:<nil> ns1.example.net:<nil>] tmp := make([]interface{}, 0) for rdataK, rdataV := range rdata.(map[string]interface{}) { if rdataV == nil { rdataV = "" } tmp = append(tmp, []string{rdataK, rdataV.(string)}) } records[rType] = tmp case string: // CNAME and alias tmp := make([]interface{}, 1) tmp[0] = rdata.(string) records[rType] = tmp default: records[rType] = rdata.([]interface{}) } //log.Printf("RECORDS %s TYPE-REC %T\n", Records, Records) label.Records[dnsType] = make(Records, len(records[rType])) for i := 0; i < len(records[rType]); i++ { //log.Printf("RT %T %#v\n", records[rType][i], records[rType][i]) record := new(Record) var h dns.RR_Header // log.Println("TTL OPTIONS", Zone.Options.Ttl) h.Ttl = uint32(label.Ttl) h.Class = dns.ClassINET h.Rrtype = dnsType switch len(label.Label) { case 0: h.Name = Zone.Origin + "." default: h.Name = label.Label + "." + Zone.Origin + "." } switch dnsType { case dns.TypeA, dns.TypeAAAA: str, weight := getStringWeight(records[rType][i].([]interface{})) ip := str record.Weight = weight switch dnsType { case dns.TypeA: if x := net.ParseIP(ip); x != nil { record.RR = &dns.A{Hdr: h, A: x} break } panic(fmt.Errorf("Bad A record %s for %s", ip, dk)) case dns.TypeAAAA: if x := net.ParseIP(ip); x != nil { record.RR = &dns.AAAA{Hdr: h, AAAA: x} break } panic(fmt.Errorf("Bad AAAA record %s for %s", ip, dk)) } case dns.TypeMX: rec := records[rType][i].(map[string]interface{}) pref := uint16(0) mx := rec["mx"].(string) if !strings.HasSuffix(mx, ".") { mx = mx + "." } if rec["weight"] != nil { record.Weight = valueToInt(rec["weight"]) } if rec["preference"] != nil { pref = uint16(valueToInt(rec["preference"])) } record.RR = &dns.MX{ Hdr: h, Mx: mx, Preference: pref} case dns.TypeSRV: rec := records[rType][i].(map[string]interface{}) priority := uint16(0) srv_weight := uint16(0) port := uint16(0) target := rec["target"].(string) if !dns.IsFqdn(target) { target = target + "." + Zone.Origin } if rec["srv_weight"] != nil { srv_weight = uint16(valueToInt(rec["srv_weight"])) } if rec["port"] != nil { port = uint16(valueToInt(rec["port"])) } if rec["priority"] != nil { priority = uint16(valueToInt(rec["priority"])) } record.RR = &dns.SRV{ Hdr: h, Priority: priority, Weight: srv_weight, Port: port, Target: target} case dns.TypeCNAME: rec := records[rType][i] var target string var weight int switch rec.(type) { case string: target = rec.(string) case []interface{}: target, weight = getStringWeight(rec.([]interface{})) } if !dns.IsFqdn(target) { target = target + "." + Zone.Origin } record.Weight = weight record.RR = &dns.CNAME{Hdr: h, Target: dns.Fqdn(target)} case dns.TypeMF: rec := records[rType][i] // MF records (how we store aliases) are not FQDNs record.RR = &dns.MF{Hdr: h, Mf: rec.(string)} case dns.TypeNS: rec := records[rType][i] if h.Ttl < 86400 { h.Ttl = 86400 } var ns string switch rec.(type) { case string: ns = rec.(string) case []string: recl := rec.([]string) ns = recl[0] if len(recl[1]) > 0 { log.Println("NS records with names syntax not supported") } default: log.Printf("Data: %T %#v\n", rec, rec) panic("Unrecognized NS format/syntax") } rr := &dns.NS{Hdr: h, Ns: dns.Fqdn(ns)} record.RR = rr case dns.TypeTXT: rec := records[rType][i] var txt string switch rec.(type) { case string: txt = rec.(string) case map[string]interface{}: recmap := rec.(map[string]interface{}) if weight, ok := recmap["weight"]; ok { record.Weight = valueToInt(weight) } if t, ok := recmap["txt"]; ok { txt = t.(string) } } if len(txt) > 0 { rr := &dns.TXT{Hdr: h, Txt: []string{txt}} record.RR = rr } else { log.Printf("Zero length txt record for '%s' in '%s'\n", label.Label, Zone.Origin) continue } // Initial SPF support added here, cribbed from the TypeTXT case definition - SPF records should be handled identically case dns.TypeSPF: rec := records[rType][i] var spf string switch rec.(type) { case string: spf = rec.(string) case map[string]interface{}: recmap := rec.(map[string]interface{}) if weight, ok := recmap["weight"]; ok { record.Weight = valueToInt(weight) } if t, ok := recmap["spf"]; ok { spf = t.(string) } } if len(spf) > 0 { rr := &dns.SPF{Hdr: h, Txt: []string{spf}} record.RR = rr } else { log.Printf("Zero length SPF record for '%s' in '%s'\n", label.Label, Zone.Origin) continue } default: log.Println("type:", rType) panic("Don't know how to handle this type") } if record.RR == nil { panic("record.RR is nil") } label.Weight[dnsType] += record.Weight label.Records[dnsType][i] = *record } if label.Weight[dnsType] > 0 { sort.Sort(RecordsByWeight{label.Records[dnsType]}) } } } // loop over exisiting labels, create zone records for missing sub-domains for k := range Zone.Labels { if strings.Contains(k, ".") { subLabels := strings.Split(k, ".") for i := 1; i < len(subLabels); i++ { subSubLabel := strings.Join(subLabels[i:len(subLabels)], ".") if _, ok := Zone.Labels[subSubLabel]; !ok { Zone.AddLabel(subSubLabel) } } } } setupSOA(Zone) //log.Println(Zones[k]) }
func serve(w dns.ResponseWriter, req *dns.Msg, z *Zone) { qtype := req.Question[0].Qtype logPrintf("[zone %s] incoming %s %s %d from %s\n", z.Origin, req.Question[0].Name, dns.TypeToString[qtype], req.MsgHdr.Id, w.RemoteAddr()) // Global meter metrics.Get("queries").(metrics.Meter).Mark(1) // Zone meter z.Metrics.Queries.Mark(1) logPrintln("Got request", req) label := getQuestionName(z, req) z.Metrics.LabelStats.Add(label) realIp, _, _ := net.SplitHostPort(w.RemoteAddr().String()) z.Metrics.ClientStats.Add(realIp) var ip net.IP // EDNS or real IP var edns *dns.EDNS0_SUBNET var opt_rr *dns.OPT for _, extra := range req.Extra { switch extra.(type) { case *dns.OPT: for _, o := range extra.(*dns.OPT).Option { opt_rr = extra.(*dns.OPT) switch e := o.(type) { case *dns.EDNS0_NSID: // do stuff with e.Nsid case *dns.EDNS0_SUBNET: z.Metrics.EdnsQueries.Mark(1) logPrintln("Got edns", e.Address, e.Family, e.SourceNetmask, e.SourceScope) if e.Address != nil { edns = e ip = e.Address } } } } } if len(ip) == 0 { // no edns subnet ip = net.ParseIP(realIp) } targets, netmask := z.Options.Targeting.GetTargets(ip) m := new(dns.Msg) m.SetReply(req) if e := m.IsEdns0(); e != nil { m.SetEdns0(4096, e.Do()) } m.Authoritative = true // TODO: set scope to 0 if there are no alternate responses if edns != nil { if edns.Family != 0 { if netmask < 16 { netmask = 16 } edns.SourceScope = uint8(netmask) m.Extra = append(m.Extra, opt_rr) } } labels, labelQtype := z.findLabels(label, targets, qTypes{dns.TypeMF, dns.TypeCNAME, qtype}) if labelQtype == 0 { labelQtype = qtype } if labels == nil { firstLabel := (strings.Split(label, "."))[0] if firstLabel == "_status" { if qtype == dns.TypeANY || qtype == dns.TypeTXT { m.Answer = statusRR(label + "." + z.Origin + ".") } else { m.Ns = append(m.Ns, z.SoaRR()) } m.Authoritative = true w.WriteMsg(m) return } if firstLabel == "_country" { if qtype == dns.TypeANY || qtype == dns.TypeTXT { h := dns.RR_Header{Ttl: 1, Class: dns.ClassINET, Rrtype: dns.TypeTXT} h.Name = label + "." + z.Origin + "." txt := []string{ w.RemoteAddr().String(), ip.String(), } targets, netmask := z.Options.Targeting.GetTargets(ip) txt = append(txt, strings.Join(targets, " ")) txt = append(txt, fmt.Sprintf("/%d", netmask), serverID, serverIP) m.Answer = []dns.RR{&dns.TXT{Hdr: h, Txt: txt, }} } else { m.Ns = append(m.Ns, z.SoaRR()) } m.Authoritative = true w.WriteMsg(m) return } // return NXDOMAIN m.SetRcode(req, dns.RcodeNameError) m.Authoritative = true m.Ns = []dns.RR{z.SoaRR()} w.WriteMsg(m) return } if servers := labels.Picker(labelQtype, labels.MaxHosts); servers != nil { var rrs []dns.RR for _, record := range servers { rr := record.RR rr.Header().Name = req.Question[0].Name rrs = append(rrs, rr) } m.Answer = rrs } if len(m.Answer) == 0 { m.Ns = append(m.Ns, z.SoaRR()) } logPrintln(m) err := w.WriteMsg(m) if err != nil { // if Pack'ing fails the Write fails. Return SERVFAIL. log.Println("Error writing packet", m) dns.HandleFailed(w, req) } return }