func (s *server) signSet(r []dns.RR, now time.Time, incep, expir uint32) (*dns.RRSIG, error) { key := cache.Key(r) if m, exp, hit := s.scache.Search(key); hit { // There can only be one sig in this cache. // Is it still valid 24 hours from now? if now.Add(+24*time.Hour).Sub(exp) < -24*time.Hour { return m.Answer[0].(*dns.RRSIG), nil } s.scache.Remove(key) } logf("scache miss for %s type %d", r[0].Header().Name, r[0].Header().Rrtype) StatsDnssecCacheMiss.Inc(1) promCacheMiss.WithLabelValues("signature").Inc() sig, err, shared := inflight.Do(key, func() (*dns.RRSIG, error) { sig1 := s.NewRRSIG(incep, expir) sig1.Header().Ttl = r[0].Header().Ttl if r[0].Header().Rrtype == dns.TypeTXT { sig1.OrigTtl = 0 } e := sig1.Sign(s.config.PrivKey, r) if e != nil { logf("failed to sign: %s", e.Error()) } return sig1, e }) if err != nil { return nil, err } if !shared { s.scache.InsertSignature(key, sig) } return dns.Copy(sig).(*dns.RRSIG), nil }
func (s *server) signSet(r []dns.RR, now time.Time, incep, expir uint32) (*dns.RRSIG, error) { key := cache.key(r) if sig := cache.search(key); sig != nil { // Is it still valid 24 hours from now? if sig.ValidityPeriod(now.Add(+24 * time.Hour)) { return sig, nil } cache.remove(key) } s.config.log.Infof("cache miss for %s type %d", r[0].Header().Name, r[0].Header().Rrtype) StatsDnssecCacheMiss.Inc(1) sig, err, shared := inflight.Do(key, func() (*dns.RRSIG, error) { sig1 := s.NewRRSIG(incep, expir) sig1.Header().Ttl = r[0].Header().Ttl if r[0].Header().Rrtype == dns.TypeTXT { sig1.OrigTtl = 0 } e := sig1.Sign(s.config.PrivKey, r) if e != nil { s.config.log.Errorf("failed to sign: %s", e.Error()) } return sig1, e }) if err != nil { return nil, err } if !shared { cache.insert(key, sig) } return dns.Copy(sig).(*dns.RRSIG), nil }
func copyRRSlice(a []dns.RR) []dns.RR { b := make([]dns.RR, 0, len(a)) for _, rr := range a { b = append(b, dns.Copy(rr)) } return b }
func (s *server) signSet(r []dns.RR, now time.Time, incep, expir uint32) (*dns.RRSIG, error) { key := cache.KeyRRset(r) if m, exp, hit := s.scache.Search(key); hit { // There can only be one sig in this cache. // Is it still valid 24 hours from now? if now.Add(+24*time.Hour).Sub(exp) < -24*time.Hour { return m.Answer[0].(*dns.RRSIG), nil } s.scache.Remove(key) } if s.config.Verbose { logf("scache miss for %s type %d", r[0].Header().Name, r[0].Header().Rrtype) } metrics.ReportCacheMiss("signature") sig, err := inflight.Do(key, func() (interface{}, error) { sig1 := s.NewRRSIG(incep, expir) sig1.Header().Ttl = r[0].Header().Ttl if r[0].Header().Rrtype == dns.TypeTXT { sig1.OrigTtl = 0 } e := sig1.Sign(s.config.PrivKey, r) if e != nil { logf("failed to sign: %s", e.Error()) } return sig1, e }) if err != nil { return nil, err } s.scache.InsertSignature(key, sig.(*dns.RRSIG)) return dns.Copy(sig.(*dns.RRSIG)).(*dns.RRSIG), nil }
// cacheCopy performs a deep copy of the given resource records func cacheCopy(rr []dns.RR) []dns.RR { clone := make([]dns.RR, len(rr)) for i := range rr { clone[i] = dns.Copy(rr[i]) } return clone }
// wildcardReplace replaces the ownername with the original query name. func wildcardReplace(qname, ce string, rrs []dns.RR) []dns.RR { // need to copy here, otherwise we change in zone stuff ret := make([]dns.RR, len(rrs)) for i, r := range rrs { ret[i] = dns.Copy(r) ret[i].Header().Name = qname } return ret }
func (g LimitAnswers) Do(request, response *dns.Msg) { answers := make([]dns.RR, 0) for i, answer := range response.Answer { if i >= g.Limit { break } answers = append(answers, dns.Copy(answer)) } response.Answer = answers }
func (c *sigCache) search(s string) *dns.RRSIG { c.RLock() defer c.RUnlock() if s, ok := c.m[s]; ok { // we want to return a copy here, because if we didn't the RRSIG // could be removed by another goroutine before the packet containing // this signature is send out. return dns.Copy(s).(*dns.RRSIG) } return nil }
// Search returns .... and a boolean indicating if we found something // in the cache. func (c *Cache) Search(s string) ([]dns.RR, []dns.RR, time.Time, bool) { if c.capacity == 0 { return nil, nil, time.Time{}, false } c.Lock() defer c.Unlock() if e, ok := c.m[s]; ok { c.l.MoveToFront(e) e := e.Value.(*elem) answer := make([]dns.RR, len(e.answer)) extra := make([]dns.RR, len(e.extra)) for i, r := range e.answer { // we want to return a copy here, because if we didn't the RRSIG // could be removed by another goroutine before the packet containing // this signature is send out. answer[i] = dns.Copy(r) } for i, r := range e.extra { extra[i] = dns.Copy(r) } return answer, extra, e.expiration, true } return nil, nil, time.Time{}, false }
// InsertSignature inserts a signature, the expiration time is used as the cache ttl. func (c *Cache) InsertSignature(s string, sig *dns.RRSIG) { if c.capacity <= 0 { return } c.Lock() if _, ok := c.m[s]; !ok { m := ((int64(sig.Expiration) - time.Now().Unix()) / (1 << 31)) - 1 if m < 0 { m = 0 } t := time.Unix(int64(sig.Expiration)-(m*(1<<31)), 0).UTC() c.m[s] = &elem{t, &dns.Msg{Answer: []dns.RR{dns.Copy(sig)}}} } c.EvictRandom() c.Unlock() }
// getDNSKEY returns the correct DNSKEY to the client. Signatures are added when do is true. func (d Dnssec) getDNSKEY(state middleware.State, zone string, do bool) *dns.Msg { keys := make([]dns.RR, len(d.keys)) for i, k := range d.keys { keys[i] = dns.Copy(k.K) keys[i].Header().Name = zone } m := new(dns.Msg) m.SetReply(state.Req) m.Answer = keys if !do { return m } incep, expir := incepExpir(time.Now().UTC()) if sigs, err := d.sign(keys, zone, 3600, incep, expir); err == nil { m.Answer = append(m.Answer, sigs...) } return m }
func TestMarshalCanonicalCAASet(t *testing.T) { a, b := new(dns.CAA), new(dns.CAA) a.Value, b.Value = "a", "b" setA := []*dns.CAA{a, b} setB := []*dns.CAA{b, a} canonA, err := marshalCanonicalCAASet(setA) test.AssertNotError(t, err, "marshalCanonicalCAASet failed") canonB, err := marshalCanonicalCAASet(setB) test.AssertNotError(t, err, "marshalCanonicalCAASet failed") test.Assert(t, bytes.Equal(canonA, canonB), "sets do not match") cRR := dns.Copy(b) c := cRR.(*dns.CAA) c.Value = "c" c.Hdr.Ttl = 100 hashC, err := marshalCanonicalCAASet([]*dns.CAA{c, a}) test.AssertNotError(t, err, "marshalCanonicalCAASet failed") test.AssertEquals(t, c.Hdr.Ttl, uint32(100)) test.Assert(t, bytes.Equal(canonA, canonB), fmt.Sprintf("Mismatching sets had same bytes: %x == %x", hashC, canonB)) }
func (g ReplaceType) Do(request, response *dns.Msg) { from_type := "*" to_type := "" if strings.Index(g.Type, ":") >= 0 { fields := strings.Split(g.Type, ":") from_type, to_type = fields[0], fields[1] } else { to_type = g.Type } for i, answer := range response.Answer { answer = dns.Copy(answer) header := answer.Header() if from_type == "*" || header.Rrtype == dns.StringToType[from_type] { header.Rrtype = dns.StringToType[to_type] } response.Answer[i] = answer } }
func (srv *Server) serve(w dns.ResponseWriter, req *dns.Msg, z *Zone) { qname := req.Question[0].Name qtype := req.Question[0].Qtype var qle *querylog.Entry if srv.queryLogger != nil { qle = &querylog.Entry{ Time: time.Now().UnixNano(), Origin: z.Origin, Name: qname, Qtype: qtype, } defer srv.queryLogger.Write(qle) } logPrintf("[zone %s] incoming %s %s (id %d) from %s\n", z.Origin, qname, dns.TypeToString[qtype], req.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) // IP that's talking to us (not EDNS CLIENT SUBNET) var realIP net.IP if addr, ok := w.RemoteAddr().(*net.UDPAddr); ok { realIP = make(net.IP, len(addr.IP)) copy(realIP, addr.IP) } else if addr, ok := w.RemoteAddr().(*net.TCPAddr); ok { realIP = make(net.IP, len(addr.IP)) copy(realIP, addr.IP) } if qle != nil { qle.RemoteAddr = realIP.String() } z.Metrics.ClientStats.Add(realIP.String()) 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 qle != nil { qle.HasECS = true qle.ClientAddr = fmt.Sprintf("%s/%d", ip, e.SourceNetmask) } } } } } } if len(ip) == 0 { // no edns subnet ip = realIP if qle != nil { qle.ClientAddr = fmt.Sprintf("%s/%d", ip, len(ip)*8) } } targets, netmask := z.Options.Targeting.GetTargets(ip) if qle != nil { qle.Targets = targets } m := new(dns.Msg) if qle != nil { defer func() { qle.Rcode = m.Rcode qle.Answers = len(m.Answer) }() } 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 { permitDebug := !*flagPrivateDebug || (realIP != nil && realIP.IsLoopback()) firstLabel := (strings.Split(label, "."))[0] if qle != nil { qle.LabelName = firstLabel } if permitDebug && 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 := dns.Copy(record.RR) rr.Header().Name = qname rrs = append(rrs, rr) } m.Answer = rrs } if len(m.Answer) == 0 { // Return a SOA so the NOERROR answer gets cached m.Ns = append(m.Ns, z.SoaRR()) } logPrintln(m) if qle != nil { qle.LabelName = labels.Label qle.Answers = len(m.Answer) qle.Rcode = m.Rcode } 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 }
// sign signs a message m, it takes care of negative or nodata responses as // well by synthesising NSEC3 records. It will also cache the signatures, using // a hash of the signed data as a key. // We also fake the origin TTL in the signature, because we don't want to // throw away signatures when services decide to have longer TTL. So we just // set the origTTL to 60. func (s *server) sign(m *dns.Msg, bufsize uint16) { now := time.Now().UTC() incep := uint32(now.Add(-2 * time.Hour).Unix()) // 2 hours, be sure to catch daylight saving time and such expir := uint32(now.Add(7 * 24 * time.Hour).Unix()) // sign for a week // TODO(miek): repeating this two times? for _, r := range rrSets(m.Answer) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } key := cache.key(r) if s := cache.search(key); s != nil { if s.ValidityPeriod(now.Add(-24 * time.Hour)) { m.Answer = append(m.Answer, s) continue } cache.remove(key) } sig, err, shared := inflight.Do(key, func() (*dns.RRSIG, error) { sig1 := s.NewRRSIG(incep, expir) if r[0].Header().Rrtype == dns.TypeNSEC3 { sig1.OrigTtl = s.config.MinTtl sig1.Header().Ttl = s.config.MinTtl } e := sig1.Sign(s.config.PrivKey, r) if e != nil { log.Printf("failed to sign: %s\n", e.Error()) } return sig1, e }) if err != nil { continue } if !shared { // is it possible to miss this, due the the c.dups > 0 in Do()? TODO(miek) cache.insert(key, sig) } m.Answer = append(m.Answer, dns.Copy(sig).(*dns.RRSIG)) } for _, r := range rrSets(m.Ns) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } key := cache.key(r) if s := cache.search(key); s != nil { if s.ValidityPeriod(now.Add(-24 * time.Hour)) { m.Ns = append(m.Ns, s) continue } cache.remove(key) } sig, err, shared := inflight.Do(key, func() (*dns.RRSIG, error) { sig1 := s.NewRRSIG(incep, expir) if r[0].Header().Rrtype == dns.TypeNSEC3 { sig1.OrigTtl = s.config.MinTtl sig1.Header().Ttl = s.config.MinTtl } e := sig1.Sign(s.config.PrivKey, r) if e != nil { log.Printf("failed to sign: %s\n", e.Error()) } return sig1, e }) if err != nil { continue } if !shared { // is it possible to miss this, due the the c.dups > 0 in Do()? TODO(miek) cache.insert(key, sig) } m.Ns = append(m.Ns, dns.Copy(sig).(*dns.RRSIG)) } // TODO(miek): Forget the additional section for now if bufsize >= 512 || bufsize <= 4096 { m.Truncated = m.Len() > int(bufsize) } o := new(dns.OPT) o.Hdr.Name = "." o.Hdr.Rrtype = dns.TypeOPT o.SetDo() o.SetUDPSize(4096) m.Extra = append(m.Extra, o) return }
/* For fragmentation, we use a naive algorithm. We use the same header for every fragment, and include the same EDNS0 section in every additional section. We add one RR at a time, until our fragment is larger than 512 bytes, then we remove the last RR so that it fits in the 512 byte size limit. If we discover that one of the fragments ends up with 0 RR in it (for example because a single RR is too big), then we return a single truncated response instead of the set of fragments. We could perhaps make the process of building fragments faster by bisecting the set of RR that we include in an answer. So, if we have 8 RR we could try all, then if that is too big, 4 RR, and if that fits then 6 RR, until an optimal set of RR is found. We could also possibly produce a smaller set of responses by optimizing how we combine RR. Just taking account the various sizes is the same as the bin packing problem, which is NP-hard: https://en.wikipedia.org/wiki/Bin_packing_problem While some non-optimal but reasonable heuristics exist, in the case of DNS we would have to use some sophisticated algorithm to also consider name compression. */ func frag(reply *dns.Msg) []dns.Msg { // create a return value all_frags := []dns.Msg{} HasEdns0 := true // get each RR section and save a copy out remaining_answer := make([]dns.RR, len(reply.Answer)) copy(remaining_answer, reply.Answer) remaining_ns := make([]dns.RR, len(reply.Ns)) copy(remaining_ns, reply.Ns) remaining_extra := make([]dns.RR, len(reply.Extra)) copy(remaining_extra, reply.Extra) // if we don't have EDNS0 in the packet, add it now if reply.IsEdns0() == nil { reply.SetEdns0(512, false) } // the EDNS option for later use var edns0_rr dns.RR = nil // remove the EDNS0 option from our additional ("extra") section // (we will include it separately on every fragment) for ofs, r := range remaining_extra { // found the EDNS option if r.Header().Rrtype == dns.TypeOPT { // save the EDNS option edns0_rr = r // remove from the set of extra RR remaining_extra = append(remaining_extra[0:ofs], remaining_extra[ofs+1:]...) // in principle we should only have one EDNS0 section break } } if edns0_rr == nil { log.Printf("Server reply missing EDNS0 option") return []dns.Msg{} //HasEdns0 = false } // now build fragments for { // make a shallow copy of our reply packet, and prepare space for our RR frag := *reply frag.Answer = []dns.RR{} frag.Ns = []dns.RR{} frag.Extra = []dns.RR{} // add our custom EDNS0 option (needed in every fragment) local_opt := new(dns.EDNS0_LOCAL) local_opt.Code = dns.EDNS0LOCALSTART + 1 local_opt.Data = []byte{0, 0} if HasEdns0 == true { edns0_rr_copy := dns.Copy(edns0_rr) edns0_rr_copy.(*dns.OPT).Option = append(edns0_rr_copy.(*dns.OPT).Option, local_opt) frag.Extra = append(frag.Extra, edns0_rr_copy) } //if HasEdns0 == false { // frag.Extra = append(frag.Extra, local_opt) //} // add as many RR to the answer as we can for len(remaining_answer) > 0 { frag.Answer = append(frag.Answer, remaining_answer[0]) if frag.Len() <= 512 { // if the new answer fits, then remove it from our remaining list remaining_answer = remaining_answer[1:] } else { // otherwise we are full, remove it from our fragment and stop frag.Answer = frag.Answer[0 : len(frag.Answer)-1] break } } for len(remaining_ns) > 0 { frag.Ns = append(frag.Ns, remaining_ns[0]) if frag.Len() <= 512 { // if the new answer fits, then remove it from our remaining list remaining_ns = remaining_ns[1:] } else { // otherwise we are full, remove it from our fragment and stop frag.Ns = frag.Ns[0 : len(frag.Ns)-1] break } } for len(remaining_extra) > 0 { frag.Extra = append(frag.Extra, remaining_extra[0]) if frag.Len() <= 512 { // if the new answer fits, then remove it from our remaining list remaining_extra = remaining_extra[1:] } else { // otherwise we are full, remove it from our fragment and stop frag.Extra = frag.Extra[0 : len(frag.Extra)-1] break } } // check to see if we didn't manage to add any RR if (len(frag.Answer) == 0) && (len(frag.Ns) == 0) && (len(frag.Extra) == 1) { // TODO: test this :) // return a single truncated fragment without any RR frag.MsgHdr.Truncated = true frag.Extra = []dns.RR{} return []dns.Msg{frag} } // add to our list of fragments all_frags = append(all_frags, frag) // if we have finished all remaining sections, we are done if (len(remaining_answer) == 0) && (len(remaining_ns) == 0) && (len(remaining_extra) == 0) { break } } // fix up our fragments so they have the correct sequence and length values for n, frag := range all_frags { frag_edns0 := frag.IsEdns0() for _, opt := range frag_edns0.Option { if opt.Option() == dns.EDNS0LOCALSTART+1 { opt.(*dns.EDNS0_LOCAL).Data = []byte{byte(len(all_frags)), byte(n)} } } } // return our fragments return all_frags }