Add scoping to IPInfo
This commit is contained in:
@@ -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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user