Add scoping to IPInfo

This commit is contained in:
Daniel
2020-10-13 16:05:05 +02:00
parent 86fed20f71
commit 62dd4355be
7 changed files with 155 additions and 106 deletions

View File

@@ -7,12 +7,25 @@ import (
"github.com/safing/portbase/database"
"github.com/safing/portbase/database/record"
"github.com/safing/portbase/utils"
)
const (
IPInfoScopeGlobal = "global"
)
var (
ipInfoDatabase = database.NewInterface(&database.Options{
AlwaysSetRelativateExpiry: 86400, // 24 hours
Local: true,
Internal: true,
// Cache entries because new/updated entries will often be queries soon
// after inserted.
CacheSize: 256,
// We only use the cache database here, so we can delay and batch all our
// writes. Also, no one else accesses these records, so we are fine using
// this.
DelayCachedWrites: "cache",
})
)
@@ -25,6 +38,11 @@ type ResolvedDomain struct {
// CNAMEs is a list of CNAMEs that have been resolved for
// Domain.
CNAMEs []string
// Expires holds the timestamp when this entry expires.
// This does not mean that the entry may not be used anymore afterwards,
// but that this is used to calcuate the TTL of the database record.
Expires int64
}
// String returns a string representation of ResolvedDomain including
@@ -54,29 +72,17 @@ func (rds ResolvedDomains) String() string {
return strings.Join(domains, " or ")
}
// MostRecentDomain returns the most recent domain.
func (rds ResolvedDomains) MostRecentDomain() *ResolvedDomain {
if len(rds) == 0 {
return nil
}
// TODO(ppacher): we could also do that by using ResolvedAt()
mostRecent := rds[len(rds)-1]
return &mostRecent
}
// IPInfo represents various information about an IP.
type IPInfo struct {
record.Base
sync.Mutex
// IP holds the acutal IP address.
// IP holds the actual IP address.
IP string
// Domains holds a list of domains that have been
// resolved to IP. This field is deprecated and should
// be removed.
// DEPRECATED: remove with alpha.
Domains []string `json:"Domains,omitempty"`
// Scope holds a scope for this IPInfo.
// Usually this would be the Profile ID of the associated process.
Scope string
// ResolvedDomain is a slice of domains that
// have been requested by various applications
@@ -84,35 +90,43 @@ type IPInfo struct {
ResolvedDomains ResolvedDomains
}
// AddDomain adds a new resolved domain to ipi.
func (ipi *IPInfo) AddDomain(resolved ResolvedDomain) bool {
for idx, d := range ipi.ResolvedDomains {
if d.Domain == resolved.Domain {
if utils.StringSliceEqual(d.CNAMEs, resolved.CNAMEs) {
return false
}
// AddDomain adds a new resolved domain to IPInfo.
func (info *IPInfo) AddDomain(resolved ResolvedDomain) {
info.Lock()
defer info.Unlock()
// we have a different CNAME chain now, remove the previous
// entry and add it at the end.
ipi.ResolvedDomains = append(ipi.ResolvedDomains[:idx], ipi.ResolvedDomains[idx+1:]...)
ipi.ResolvedDomains = append(ipi.ResolvedDomains, resolved)
return true
// Delete old for the same domain.
for idx, d := range info.ResolvedDomains {
if d.Domain == resolved.Domain {
info.ResolvedDomains = append(info.ResolvedDomains[:idx], info.ResolvedDomains[idx+1:]...)
break
}
}
ipi.ResolvedDomains = append(ipi.ResolvedDomains, resolved)
return true
// Add new entry to the end.
info.ResolvedDomains = append(info.ResolvedDomains, resolved)
}
func makeIPInfoKey(ip string) string {
return fmt.Sprintf("cache:intel/ipInfo/%s", ip)
// MostRecentDomain returns the most recent domain.
func (info *IPInfo) MostRecentDomain() *ResolvedDomain {
info.Lock()
defer info.Unlock()
if len(info.ResolvedDomains) == 0 {
return nil
}
mostRecent := info.ResolvedDomains[len(info.ResolvedDomains)-1]
return &mostRecent
}
func makeIPInfoKey(scope, ip string) string {
return fmt.Sprintf("cache:intel/ipInfo/%s/%s", scope, ip)
}
// GetIPInfo gets an IPInfo record from the database.
func GetIPInfo(ip string) (*IPInfo, error) {
key := makeIPInfoKey(ip)
r, err := ipInfoDatabase.Get(key)
func GetIPInfo(scope, ip string) (*IPInfo, error) {
r, err := ipInfoDatabase.Get(makeIPInfoKey(scope, ip))
if err != nil {
return nil, err
}
@@ -126,18 +140,6 @@ func GetIPInfo(ip string) (*IPInfo, error) {
return nil, err
}
// Legacy support,
// DEPRECATED: remove with alpha
if len(new.Domains) > 0 && len(new.ResolvedDomains) == 0 {
for _, d := range new.Domains {
new.ResolvedDomains = append(new.ResolvedDomains, ResolvedDomain{
Domain: d,
// rest is empty...
})
}
new.Domains = nil // clean up so we remove it from the database
}
return new, nil
}
@@ -150,27 +152,38 @@ func GetIPInfo(ip string) (*IPInfo, error) {
}
// Save saves the IPInfo record to the database.
func (ipi *IPInfo) Save() error {
ipi.Lock()
if !ipi.KeyIsSet() {
ipi.SetKey(makeIPInfoKey(ipi.IP))
}
ipi.Unlock()
func (info *IPInfo) Save() error {
info.Lock()
// Legacy support
// Ensure we don't write new Domain fields into the
// database.
// DEPRECATED: remove with alpha
if len(ipi.Domains) > 0 {
ipi.Domains = nil
// Set database key if not yet set already.
if !info.KeyIsSet() {
// Default to global scope if scope is unset.
if info.Scope == "" {
info.Scope = IPInfoScopeGlobal
}
info.SetKey(makeIPInfoKey(info.Scope, info.IP))
}
return ipInfoDatabase.Put(ipi)
// Calculate and set cache expiry.
var expires int64 = 86400 // Minimum TTL of one day.
for _, rd := range info.ResolvedDomains {
if rd.Expires > expires {
expires = rd.Expires
}
}
info.UpdateMeta()
expires += 3600 // Add one hour to expiry as a buffer.
info.Meta().SetAbsoluteExpiry(expires)
info.Unlock()
return ipInfoDatabase.Put(info)
}
// FmtDomains returns a string consisting of the domains that have seen to use this IP, joined by " or "
func (ipi *IPInfo) String() string {
ipi.Lock()
defer ipi.Unlock()
return fmt.Sprintf("<IPInfo[%s] %s: %s", ipi.Key(), ipi.IP, ipi.ResolvedDomains.String())
func (info *IPInfo) String() string {
info.Lock()
defer info.Unlock()
return fmt.Sprintf("<IPInfo[%s] %s: %s>", info.Key(), info.IP, info.ResolvedDomains.String())
}