From ca74ead2771b8c34eac8ee446591a635918c7ee4 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Thu, 19 Nov 2020 17:55:50 -0800 Subject: [PATCH] Add -cache-timeout command line switch. So far, our cache timeout was hard-coded to be one week. Now that bridgestrap is more efficient, we can afford to test resources more often, so this patch changes the default timeout to 24 hours. The patch also makes this timeout configurable through the new -cache-timeout switch. While at it, the patch also refactored our cache mechanism a bit and renamed -timeout to -test-timeout. This patch fixes tpo/anti-censorship/bridgestrap#8 --- cache.go | 55 +++++++++++++++++++++++++-------------------------- cache_test.go | 29 ++++++++++++++++----------- main.go | 18 ++++++++++++----- tor_test.go | 2 ++ 4 files changed, 60 insertions(+), 44 deletions(-) diff --git a/cache.go b/cache.go index e357e6a..7c4d93d 100644 --- a/cache.go +++ b/cache.go @@ -10,13 +10,7 @@ import ( "time" ) -const ( - // Cache test results for one week. - CacheValidity = 7 * 24 * time.Hour -) - -var cacheMutex sync.Mutex -var cache TestCache = make(TestCache) +var cache *TestCache // Regular expression that captures the address:port part of a bridge line (for // both IPv4 and IPv6 addresses). @@ -33,6 +27,14 @@ type CacheEntry struct { Time time.Time } +type TestCache struct { + // Entries maps a bridge's addr:port tuple to a cache entry. + Entries map[string]*CacheEntry + // EntryTimeout determines how long a cache entry is valid for. + EntryTimeout time.Duration + l sync.Mutex +} + // bridgeLineToAddrPort takes a bridge line as input and returns a string // consisting of the bridge's addr:port (for both IPv4 and IPv6 addresses). func bridgeLineToAddrPort(bridgeLine string) (string, error) { @@ -45,9 +47,6 @@ func bridgeLineToAddrPort(bridgeLine string) (string, error) { } } -// TestCache maps a bridge's addr:port tuple to a cache entry. -type TestCache map[string]*CacheEntry - // WriteToDisk writes our test result cache to disk, allowing it to persist // across program restarts. func (tc *TestCache) WriteToDisk(cacheFile string) error { @@ -59,13 +58,13 @@ func (tc *TestCache) WriteToDisk(cacheFile string) error { defer fh.Close() enc := gob.NewEncoder(fh) - cacheMutex.Lock() + tc.l.Lock() err = enc.Encode(*tc) if err == nil { log.Printf("Wrote cache with %d elements to %q.", - len(*tc), cacheFile) + len((*tc).Entries), cacheFile) } - cacheMutex.Unlock() + tc.l.Unlock() return err } @@ -80,39 +79,39 @@ func (tc *TestCache) ReadFromDisk(cacheFile string) error { defer fh.Close() dec := gob.NewDecoder(fh) - cacheMutex.Lock() + tc.l.Lock() err = dec.Decode(tc) if err == nil { log.Printf("Read cache with %d elements from %q.", - len(*tc), cacheFile) + len((*tc).Entries), cacheFile) } - cacheMutex.Unlock() + tc.l.Unlock() return err } // IsCached returns a cache entry if the given bridge line has been tested -// recently (as determined by CacheValidity), and nil otherwise. +// recently (as determined by EntryTimeout), and nil otherwise. func (tc *TestCache) IsCached(bridgeLine string) *CacheEntry { // First, prune expired cache entries. now := time.Now().UTC() - cacheMutex.Lock() - for index, entry := range *tc { - if entry.Time.Before(now.Add(-CacheValidity)) { - delete(*tc, index) + tc.l.Lock() + for index, entry := range (*tc).Entries { + if entry.Time.Before(now.Add(-(*tc).EntryTimeout)) { + delete((*tc).Entries, index) } } - cacheMutex.Unlock() + tc.l.Unlock() addrPort, err := bridgeLineToAddrPort(bridgeLine) if err != nil { return nil } - cacheMutex.Lock() - var r *CacheEntry = (*tc)[addrPort] - cacheMutex.Unlock() + tc.l.Lock() + var r *CacheEntry = (*tc).Entries[addrPort] + tc.l.Unlock() return r } @@ -132,7 +131,7 @@ func (tc *TestCache) AddEntry(bridgeLine string, result error, lastTested time.T } else { errorStr = result.Error() } - cacheMutex.Lock() - (*tc)[addrPort] = &CacheEntry{errorStr, lastTested} - cacheMutex.Unlock() + tc.l.Lock() + (*tc).Entries[addrPort] = &CacheEntry{errorStr, lastTested} + tc.l.Unlock() } diff --git a/cache_test.go b/cache_test.go index 33f20dd..dad1796 100644 --- a/cache_test.go +++ b/cache_test.go @@ -11,9 +11,16 @@ import ( "time" ) +func NewCache() *TestCache { + return &TestCache{ + Entries: make(map[string]*CacheEntry), + EntryTimeout: 24 * time.Hour, + } +} + func TestCacheFunctions(t *testing.T) { - cache := make(TestCache) + cache := NewCache() bridgeLine := "obfs4 127.0.0.1:1 cert=foo iat-mode=0" e := cache.IsCached(bridgeLine) @@ -35,10 +42,10 @@ func TestCacheFunctions(t *testing.T) { } // A bogus bridge line shouldn't make it into the cache. - cache = make(TestCache) + cache = NewCache() bogusBridgeLine := "bogus-bridge-line" cache.AddEntry(bogusBridgeLine, errors.New("bogus-error"), time.Now().UTC()) - if len(cache) != 0 { + if len(cache.Entries) != 0 { t.Errorf("Bogus bridge line made it into cache.") } @@ -50,15 +57,15 @@ func TestCacheFunctions(t *testing.T) { func TestCacheExpiration(t *testing.T) { - cache := make(TestCache) + cache := NewCache() const shortForm = "2006-Jan-02" expiry, _ := time.Parse(shortForm, "2000-Jan-01") bridgeLine1 := "1.1.1.1:1111" - cache[bridgeLine1] = &CacheEntry{"", expiry} + cache.Entries[bridgeLine1] = &CacheEntry{"", expiry} bridgeLine2 := "2.2.2.2:2222" - cache[bridgeLine2] = &CacheEntry{"", time.Now().UTC()} + cache.Entries[bridgeLine2] = &CacheEntry{"", time.Now().UTC()} e := cache.IsCached(bridgeLine1) if e != nil { @@ -83,7 +90,7 @@ func BenchmarkIsCached(b *testing.B) { } numCacheEntries := 10000 - cache := make(TestCache) + cache := NewCache() for i := 0; i < numCacheEntries; i++ { cache.AddEntry(getRandAddrPort(), getRandError(), time.Now().UTC()) } @@ -97,7 +104,7 @@ func BenchmarkIsCached(b *testing.B) { func TestCacheSerialisation(t *testing.T) { - cache := make(TestCache) + cache := NewCache() testError := fmt.Errorf("foo") cache.AddEntry("1.1.1.1:1", testError, time.Now().UTC()) cache.AddEntry("2.2.2.2:2", fmt.Errorf("bar"), time.Now().UTC()) @@ -117,8 +124,8 @@ func TestCacheSerialisation(t *testing.T) { t.Errorf("Failed to read cache from disk: %s", err) } - if len(cache) != 2 { - t.Errorf("Cache supposed to contain but two elements but has %d.", len(cache)) + if len(cache.Entries) != 2 { + t.Errorf("Cache supposed to contain but two elements but has %d.", len(cache.Entries)) } e1 := cache.IsCached("1.1.1.1:1") @@ -140,7 +147,7 @@ func TestCacheSerialisation(t *testing.T) { func TestCacheConcurrency(t *testing.T) { - cache := make(TestCache) + cache := NewCache() max := 10000 doneReading := make(chan bool) doneWriting := make(chan bool) diff --git a/main.go b/main.go index 4462460..3e052eb 100644 --- a/main.go +++ b/main.go @@ -94,7 +94,7 @@ func printPrettyCache() { var shortError string var numFunctional int - for bridgeLine, cacheEntry := range cache { + for bridgeLine, cacheEntry := range cache.Entries { shortError = cacheEntry.Error maxChars := 50 if len(cacheEntry.Error) > maxChars { @@ -105,9 +105,10 @@ func printPrettyCache() { } fmt.Printf("%-22s %-50s %s\n", bridgeLine, shortError, cacheEntry.Time) } - if len(cache) > 0 { + cacheLen := len(cache.Entries) + if len(cache.Entries) > 0 { log.Printf("Found %d (%.2f%%) out of %d functional.\n", numFunctional, - float64(numFunctional)/float64(len(cache))*100.0, len(cache)) + float64(numFunctional)/float64(cacheLen)*100.0, cacheLen) } } @@ -120,7 +121,7 @@ func main() { var cacheFile string var templatesDir string var torBinary string - var testTimeout int + var testTimeout, cacheTimeout int var logFile string flag.StringVar(&addr, "addr", ":5000", "Address to listen on.") @@ -134,7 +135,8 @@ func main() { flag.StringVar(&templatesDir, "templates", "templates", "Path to directory that contains our web templates.") flag.StringVar(&torBinary, "tor", "tor", "Path to tor executable.") flag.StringVar(&logFile, "log", "", "File to write logs to.") - flag.IntVar(&testTimeout, "timeout", 60, "Test timeout in seconds.") + flag.IntVar(&testTimeout, "test-timeout", 60, "Test timeout in seconds.") + flag.IntVar(&cacheTimeout, "cache-timeout", 24, "Cache timeout in hours.") flag.Parse() if showVersion { @@ -179,6 +181,12 @@ func main() { return } + cache = &TestCache{ + Entries: make(map[string]*CacheEntry), + EntryTimeout: time.Duration(cacheTimeout) * time.Hour, + } + log.Printf("Setting cache timeout to %s.", cache.EntryTimeout) + TorTestTimeout = time.Duration(testTimeout) * time.Second log.Printf("Setting Tor test timeout to %s.", TorTestTimeout) torCtx = &TorContext{TorBinary: torBinary} diff --git a/tor_test.go b/tor_test.go index 4a02cfc..4895915 100644 --- a/tor_test.go +++ b/tor_test.go @@ -3,6 +3,7 @@ package main import ( "bytes" "testing" + "time" ) func TestWriteConfigToTorrc(t *testing.T) { @@ -54,6 +55,7 @@ func TestBridgeTest(t *testing.T) { defaultBridge2 := "obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0" bogusBridge := "127.0.0.1:1" + TorTestTimeout = time.Minute torCtx = &TorContext{TorBinary: "tor"} if err := torCtx.Start(); err != nil { t.Fatalf("Failed to start tor: %s", err) -- GitLab