mirror of
https://github.com/safing/portmaster
synced 2025-09-04 19:49:15 +00:00
218 lines
5.4 KiB
Go
218 lines
5.4 KiB
Go
// 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
|
|
}
|