// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. package intel import ( "fmt" "net" "time" "github.com/Safing/safing-core/database" datastore "github.com/ipfs/go-datastore" "github.com/miekg/dns" ) // RRCache is used to cache DNS data type RRCache struct { Answer []dns.RR Ns []dns.RR Extra []dns.RR Expires int64 Modified int64 servedFromCache bool requestingNew bool } func (m *RRCache) Clean(minExpires uint32) { var lowestTTL uint32 = 0xFFFFFFFF var header *dns.RR_Header // set TTLs to 17 // TODO: double append? is there something more elegant? for _, rr := range append(m.Answer, append(m.Ns, m.Extra...)...) { header = rr.Header() if lowestTTL > header.Ttl { lowestTTL = header.Ttl } header.Ttl = 17 } // TTL must be at least minExpires if lowestTTL < minExpires { lowestTTL = minExpires } m.Expires = time.Now().Unix() + int64(lowestTTL) m.Modified = time.Now().Unix() } func (m *RRCache) ExportAllARecords() (ips []net.IP) { for _, rr := range m.Answer { if rr.Header().Class == dns.ClassINET && rr.Header().Rrtype == dns.TypeA { aRecord, ok := rr.(*dns.A) if ok { ips = append(ips, aRecord.A) } } else if rr.Header().Class == dns.ClassINET && rr.Header().Rrtype == dns.TypeAAAA { aRecord, ok := rr.(*dns.AAAA) if ok { ips = append(ips, aRecord.AAAA) } } } return } func (m *RRCache) ToRRSave() *RRSave { var s RRSave s.Expires = m.Expires s.Modified = m.Modified for _, entry := range m.Answer { s.Answer = append(s.Answer, entry.String()) } for _, entry := range m.Ns { s.Ns = append(s.Ns, entry.String()) } for _, entry := range m.Extra { s.Extra = append(s.Extra, entry.String()) } return &s } func (m *RRCache) Create(name string) error { s := m.ToRRSave() return s.CreateObject(&database.DNSCache, name, s) } func (m *RRCache) CreateWithType(name string, qtype dns.Type) error { s := m.ToRRSave() return s.Create(fmt.Sprintf("%s%s", name, qtype.String())) } func (m *RRCache) Save() error { s := m.ToRRSave() return s.SaveObject(s) } func GetRRCache(domain string, qtype dns.Type) (*RRCache, error) { return GetRRCacheFromNamespace(&database.DNSCache, domain, qtype) } func GetRRCacheFromNamespace(namespace *datastore.Key, domain string, qtype dns.Type) (*RRCache, error) { var m RRCache rrSave, err := GetRRSaveFromNamespace(namespace, domain, qtype) if err != nil { return nil, err } m.Expires = rrSave.Expires m.Modified = rrSave.Modified for _, entry := range rrSave.Answer { rr, err := dns.NewRR(entry) if err == nil { m.Answer = append(m.Answer, rr) } } for _, entry := range rrSave.Ns { rr, err := dns.NewRR(entry) if err == nil { m.Ns = append(m.Ns, rr) } } for _, entry := range rrSave.Extra { rr, err := dns.NewRR(entry) if err == nil { m.Extra = append(m.Extra, rr) } } m.servedFromCache = true return &m, nil } // ServedFromCache marks the RRCache as served from cache. func (m *RRCache) ServedFromCache() bool { return m.servedFromCache } // RequestingNew informs that it has expired and new RRs are being fetched. func (m *RRCache) RequestingNew() bool { return m.requestingNew } // Flags formats ServedFromCache and RequestingNew to a condensed, flag-like format. func (m *RRCache) Flags() string { switch { case m.servedFromCache && m.requestingNew: return " [CR]" case m.servedFromCache: return " [C]" case m.requestingNew: return " [R]" // theoretically impossible, but let's leave it here, just in case default: return "" } } // IsNXDomain returnes whether the result is nxdomain. func (m *RRCache) IsNXDomain() bool { return len(m.Answer) == 0 } // RRSave is helper struct to RRCache to better save data to the database. type RRSave struct { database.Base Answer []string Ns []string Extra []string Expires int64 Modified int64 } var rrSaveModel *RRSave // only use this as parameter for database.EnsureModel-like functions func init() { database.RegisterModel(rrSaveModel, func() database.Model { return new(RRSave) }) } // Create saves RRSave with the provided name in the default namespace. func (m *RRSave) Create(name string) error { return m.CreateObject(&database.DNSCache, name, m) } // CreateWithType saves RRSave with the provided name and type in the default namespace. func (m *RRSave) CreateWithType(name string, qtype dns.Type) error { return m.Create(fmt.Sprintf("%s%s", name, qtype.String())) } // CreateInNamespace saves RRSave with the provided name in the provided namespace. func (m *RRSave) CreateInNamespace(namespace *datastore.Key, name string) error { return m.CreateObject(namespace, name, m) } // Save saves RRSave. func (m *RRSave) Save() error { return m.SaveObject(m) } // GetRRSave fetches RRSave with the provided name in the default namespace. func GetRRSave(name string, qtype dns.Type) (*RRSave, error) { return GetRRSaveFromNamespace(&database.DNSCache, name, qtype) } // GetRRSaveFromNamespace fetches RRSave with the provided name in the provided namespace. func GetRRSaveFromNamespace(namespace *datastore.Key, name string, qtype dns.Type) (*RRSave, error) { object, err := database.GetAndEnsureModel(namespace, fmt.Sprintf("%s%s", name, qtype.String()), rrSaveModel) if err != nil { return nil, err } model, ok := object.(*RRSave) if !ok { return nil, database.NewMismatchError(object, rrSaveModel) } return model, nil }