checking in- needs some refinement then done
This commit is contained in:
383
netsplit/funcs_cache.go
Normal file
383
netsplit/funcs_cache.go
Normal file
@@ -0,0 +1,383 @@
|
||||
package netsplit
|
||||
|
||||
import (
|
||||
`encoding/json`
|
||||
`errors`
|
||||
`io/fs`
|
||||
`net/http`
|
||||
`net/netip`
|
||||
`os`
|
||||
`path/filepath`
|
||||
`sync`
|
||||
|
||||
`github.com/go-resty/resty/v2`
|
||||
`r00t2.io/goutils/multierr`
|
||||
`r00t2.io/sysutils/envs`
|
||||
`r00t2.io/sysutils/paths`
|
||||
)
|
||||
|
||||
/*
|
||||
CacheReserved caches the IANA address/network reservations to disk
|
||||
(and updates ianaReserved4, ianaReserved6, and reservedNets).
|
||||
|
||||
It is up to the caller to schedule periodic CacheReserved calls
|
||||
according to their needs for long-lived processes.
|
||||
It *shouldn't* cause any memory leaks, but this has not been tested/confirmed.
|
||||
|
||||
If caching is not enabled, CacheReserved exits withouth any retrieval, parsing, etc.
|
||||
*/
|
||||
func CacheReserved() (err error) {
|
||||
|
||||
var dat4 []byte
|
||||
var dat6 []byte
|
||||
|
||||
if !isCaching {
|
||||
return
|
||||
}
|
||||
|
||||
if cacheDir == "" {
|
||||
if cacheDir, err = getDefCachePath(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if dat4, dat6, err = getLive(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = writeCache(dat4, dat6); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CleanCache clears the cache completely.
|
||||
func CleanCache() (err error) {
|
||||
|
||||
if cacheDir == "" {
|
||||
if cacheDir, err = getDefCachePath(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = os.RemoveAll(cacheDir); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
RetrieveReserved returns the current reservations and (re-)populates reservedNets.
|
||||
|
||||
It returns a copy of reservedNets and the current reservations that are safe to use concurrently/separately
|
||||
from subnetter.
|
||||
|
||||
If caching is enabled, then:
|
||||
|
||||
1.) First the local cache will be checked.
|
||||
|
||||
2.) If no cache exists, subnetter will attempt to populate it (CacheReserved());
|
||||
otherwise the data there will be used.
|
||||
|
||||
2.b.) If an error occurs while parsing the cached data, the cache will be invalidated
|
||||
and attempt to be updated.
|
||||
|
||||
3.) If no cache exists and the live resource is unavailable, an error will be returned.
|
||||
|
||||
If not:
|
||||
|
||||
1.) The live resource will be fetched. If it is unavailable, an error will be returned.
|
||||
*/
|
||||
func RetrieveReserved() (ipv4, ipv6 IANARegistry, reserved map[netip.Prefix]*IANAAddrNetResRecord, err error) {
|
||||
|
||||
var b []byte
|
||||
var dat4 []byte
|
||||
var dat6 []byte
|
||||
var hasCache bool
|
||||
|
||||
if isCaching {
|
||||
if hasCache, err = checkCache(); err != nil {
|
||||
return
|
||||
}
|
||||
if !hasCache {
|
||||
if err = CacheReserved(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if dat4, dat6, err = readCache(); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if dat4, dat6, err = getLive(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(dat4, &ipv4); err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(dat6, &ipv6); err != nil {
|
||||
return
|
||||
}
|
||||
ianaReserved4 = new(IANARegistry)
|
||||
ianaReserved4 = new(IANARegistry)
|
||||
if err = json.Unmarshal(dat4, ianaReserved4); err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(dat6, ianaReserved6); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reservedNets = make(map[netip.Prefix]*IANAAddrNetResRecord)
|
||||
for _, reg := range []*IANARegistry{
|
||||
ianaReserved4, ianaReserved6,
|
||||
} {
|
||||
for _, rec := range reg.Notice.Records {
|
||||
for _, n := range rec.Networks.Prefixes {
|
||||
reservedNets[*n] = rec
|
||||
}
|
||||
}
|
||||
}
|
||||
if b, err = json.Marshal(reservedNets); err != nil {
|
||||
return
|
||||
}
|
||||
reserved = make(map[netip.Prefix]*IANAAddrNetResRecord)
|
||||
if err = json.Unmarshal(b, &reserved); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetCacheConfig returns the current state and path of subnetter's cache.
|
||||
func GetCacheConfig() (enabled bool, cacheDirPath string) {
|
||||
|
||||
enabled = isCaching
|
||||
cacheDirPath = cacheDir
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// EnableCache enables or disables subnetter's caching.
|
||||
func EnableCache(enable bool) (err error) {
|
||||
|
||||
var oldVal bool = isCaching
|
||||
|
||||
isCaching = enable
|
||||
|
||||
if isCaching && (oldVal != isCaching) {
|
||||
if err = os.MkdirAll(cacheDir, 0o0640); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
SetCachePath sets the cache path. Use an empty cacheDirPath to use the default path.
|
||||
|
||||
If the cache dir was changed from its previous value, subnetter will attempt to create it.
|
||||
*/
|
||||
func SetCachePath(cacheDirPath string) (err error) {
|
||||
|
||||
var oldPath string = cacheDir
|
||||
|
||||
if cacheDirPath == "" {
|
||||
if cacheDirPath, err = getDefCachePath(); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = paths.RealPath(&cacheDirPath); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if cacheDirPath != oldPath {
|
||||
if err = os.MkdirAll(cacheDir, cacheDirPerms); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func checkCache() (hasCache bool, err error) {
|
||||
|
||||
var numCached uint8
|
||||
var cacheDirEntries []fs.DirEntry
|
||||
|
||||
if cacheDir == "" {
|
||||
if cacheDir, err = getDefCachePath(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if cacheDirEntries, err = os.ReadDir(cacheDir); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range cacheDirEntries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
switch entry.Name() {
|
||||
case ianaSpecial4Cache, ianaSpecial6Cache:
|
||||
numCached++
|
||||
}
|
||||
if numCached >= 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
hasCache = numCached > 2 && ianaReserved4 != nil && ianaReserved6 != nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getDefCachePath() (val string, err error) {
|
||||
|
||||
if envs.HasEnv(cachedirEnvName) {
|
||||
val = os.Getenv(cachedirEnvName)
|
||||
} else {
|
||||
if val, err = os.UserCacheDir(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err = paths.RealPath(&val); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getLive() (dat4, dat6 []byte, err error) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var errChan chan error
|
||||
var doneChan chan bool
|
||||
var mErr *multierr.MultiError
|
||||
var numJobs int = 2
|
||||
|
||||
doneChan = make(chan bool, 1)
|
||||
mErr = multierr.NewMultiError(nil)
|
||||
wg.Add(numJobs)
|
||||
errChan = make(chan error, numJobs)
|
||||
|
||||
if cacheClient == nil {
|
||||
cacheClient = resty.New()
|
||||
}
|
||||
|
||||
// IPv4
|
||||
go func() {
|
||||
var rErr error
|
||||
var req *resty.Request
|
||||
var resp *resty.Response
|
||||
var dat *IANARegistry = new(IANARegistry)
|
||||
|
||||
defer wg.Done()
|
||||
|
||||
req = cacheClient.R()
|
||||
req.SetResult(dat)
|
||||
|
||||
if resp, rErr = req.Get(ianaSpecial4); rErr != nil {
|
||||
errChan <- rErr
|
||||
return
|
||||
}
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
errChan <- errors.New(resp.Status())
|
||||
return
|
||||
}
|
||||
|
||||
ianaReserved4 = new(IANARegistry)
|
||||
*ianaReserved4 = *dat
|
||||
|
||||
if dat4, rErr = json.Marshal(dat); rErr != nil {
|
||||
errChan <- rErr
|
||||
return
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
// IPv6
|
||||
go func() {
|
||||
var rErr error
|
||||
var req *resty.Request
|
||||
var resp *resty.Response
|
||||
var dat *IANARegistry = new(IANARegistry)
|
||||
|
||||
defer wg.Done()
|
||||
|
||||
req = cacheClient.R()
|
||||
req.SetResult(dat)
|
||||
|
||||
if resp, rErr = req.Get(ianaSpecial6); rErr != nil {
|
||||
errChan <- rErr
|
||||
return
|
||||
}
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
errChan <- errors.New(resp.Status())
|
||||
return
|
||||
}
|
||||
|
||||
if dat6, rErr = json.Marshal(dat); rErr != nil {
|
||||
errChan <- rErr
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
doneChan <- true
|
||||
}()
|
||||
|
||||
<-doneChan
|
||||
|
||||
for i := 0; i < numJobs; i++ {
|
||||
if err = <-errChan; err != nil {
|
||||
mErr.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
if !mErr.IsEmpty() {
|
||||
err = mErr
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func readCache() (dat4, dat6 []byte, err error) {
|
||||
|
||||
if dat4, err = os.ReadFile(filepath.Join(cacheDir, ianaSpecial4Cache)); err != nil {
|
||||
return
|
||||
}
|
||||
if dat6, err = os.ReadFile(filepath.Join(cacheDir, ianaSpecial6Cache)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func writeCache(dat4, dat6 []byte) (err error) {
|
||||
|
||||
if err = os.WriteFile(
|
||||
filepath.Join(cacheDir, ianaSpecial4Cache),
|
||||
dat4,
|
||||
cacheFilePerms,
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
if err = os.WriteFile(
|
||||
filepath.Join(cacheDir, ianaSpecial6Cache),
|
||||
dat6,
|
||||
cacheFilePerms,
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user