diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2018-03-21 11:09:07 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2018-04-11 06:35:07 -0300 |
| commit | 17c306705a16914c2b1476d2ae86790271f628e3 (patch) | |
| tree | 2f0d42e417eec45cb69687183657fddc68caeb83 | |
| parent | 5eedeac9cbf6befd7ffbcd06ff767b48c8b004fe (diff) | |
| download | mullvadvpn-17c306705a16914c2b1476d2ae86790271f628e3.tar.xz mullvadvpn-17c306705a16914c2b1476d2ae86790271f628e3.zip | |
Ensure cache is periodically invalidated
| -rw-r--r-- | Cargo.lock | 12 | ||||
| -rw-r--r-- | mullvad-rpc/Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-rpc/src/cached_dns_resolver.rs | 86 |
3 files changed, 90 insertions, 9 deletions
diff --git a/Cargo.lock b/Cargo.lock index 1ae167fb80..0152eb979c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,6 +255,16 @@ dependencies = [ ] [[package]] +name = "filetime" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "fnv" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -666,6 +676,7 @@ version = "0.1.0" dependencies = [ "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.11.21 (registry+https://github.com/rust-lang/crates.io-index)", "hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1497,6 +1508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum errno 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b2c858c42ac0b88532f48fca88b0ed947cad4f1f64d904bcd6c9f138f7b95d70" "checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" "checksum fern 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "50475651fccc56343c766e4d1889428ea753308a977e1315db358ada28cc8c9d" +"checksum filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" diff --git a/mullvad-rpc/Cargo.toml b/mullvad-rpc/Cargo.toml index b76062a226..cf6360b6b0 100644 --- a/mullvad-rpc/Cargo.toml +++ b/mullvad-rpc/Cargo.toml @@ -21,4 +21,5 @@ log = "0.4" mullvad-types = { path = "../mullvad-types" } [dev-dependencies] +filetime = "0.1" tempdir = "0.3" diff --git a/mullvad-rpc/src/cached_dns_resolver.rs b/mullvad-rpc/src/cached_dns_resolver.rs index 9e25bff511..3acfad378c 100644 --- a/mullvad-rpc/src/cached_dns_resolver.rs +++ b/mullvad-rpc/src/cached_dns_resolver.rs @@ -2,6 +2,12 @@ use std::fs::File; use std::io::{self, Read, Write}; use std::net::{IpAddr, ToSocketAddrs}; use std::path::{Path, PathBuf}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + + +static MAX_CACHE_AGE: Duration = Duration::from_secs(3600); +static EXPIRED_CACHE_TIMESTAMP: SystemTime = UNIX_EPOCH; + pub trait DnsResolver { fn resolve(&self, host: &str) -> io::Result<IpAddr>; @@ -26,7 +32,7 @@ pub struct CachedDnsResolver<R: DnsResolver = SystemDnsResolver> { dns_resolver: R, cache_file: PathBuf, cached_address: IpAddr, - should_update: bool, + last_updated: SystemTime, } impl CachedDnsResolver<SystemDnsResolver> { @@ -42,7 +48,7 @@ impl<R: DnsResolver> CachedDnsResolver<R> { cache_file: PathBuf, fallback_address: IpAddr, ) -> Self { - let (cached_address, should_update) = + let (cached_address, last_updated) = Self::load_initial_cached_address(&cache_file, fallback_address); CachedDnsResolver { @@ -50,22 +56,34 @@ impl<R: DnsResolver> CachedDnsResolver<R> { dns_resolver, cache_file, cached_address, - should_update, + last_updated, } } pub fn resolve(&mut self) -> IpAddr { - if self.should_update { + if let Ok(cache_age) = self.last_updated.elapsed() { + if cache_age > MAX_CACHE_AGE { + self.resolve_into_cache(); + } + } else { self.resolve_into_cache(); } self.cached_address } - fn load_initial_cached_address(cache_file: &Path, fallback_address: IpAddr) -> (IpAddr, bool) { + fn load_initial_cached_address( + cache_file: &Path, + fallback_address: IpAddr, + ) -> (IpAddr, SystemTime) { match Self::load_from_file(cache_file) { - Ok(previously_cached_address) => (previously_cached_address, false), - Err(_) => (fallback_address, true), + Ok(previously_cached_address) => { + let last_updated = Self::read_file_modification_time(cache_file) + .unwrap_or(EXPIRED_CACHE_TIMESTAMP); + + (previously_cached_address, last_updated) + } + Err(_) => (fallback_address, EXPIRED_CACHE_TIMESTAMP), } } @@ -81,10 +99,16 @@ impl<R: DnsResolver> CachedDnsResolver<R> { .map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid address data")) } + fn read_file_modification_time(cache_file: &Path) -> Option<SystemTime> { + let metadata = cache_file.metadata().ok()?; + + metadata.modified().ok() + } + fn resolve_into_cache(&mut self) { if let Ok(address) = self.dns_resolver.resolve(&self.hostname) { self.cached_address = address; - self.should_update = false; + self.last_updated = SystemTime::now(); self.update_cache_file(); } } @@ -98,6 +122,7 @@ impl<R: DnsResolver> CachedDnsResolver<R> { #[cfg(test)] mod tests { + extern crate filetime; extern crate tempdir; use std::fs::{self, File}; @@ -105,11 +130,12 @@ mod tests { use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; + use self::filetime::FileTime; use self::tempdir::TempDir; use super::*; #[test] - fn uses_cached_address() { + fn uses_previously_cached_address() { let (_temp_dir, cache_dir) = create_test_dirs(); let mock_resolver = MockDnsResolver::with_address("192.168.1.206".parse().unwrap()); let mock_resolver_was_called = mock_resolver.was_called_handle(); @@ -125,6 +151,40 @@ mod tests { } #[test] + fn old_cache_file_is_updated() { + let (_temp_dir, cache_dir) = create_test_dirs(); + let cached_address = "127.0.0.1".parse().unwrap(); + let mock_address = "192.168.1.206".parse().unwrap(); + let mock_resolver = MockDnsResolver::with_address(mock_address); + + let cache_file_path = write_address(&cache_dir, cached_address); + + make_file_old(&cache_file_path); + + let mut cache = create_cached_dns_resolver(mock_resolver, &cache_dir, None); + let address = cache.resolve(); + + assert_eq!(get_cached_address(&cache_dir), address.to_string()); + assert_eq!(address, mock_address); + } + + #[test] + fn old_cache_file_is_used_if_resolution_fails() { + let (_temp_dir, cache_dir) = create_test_dirs(); + let mock_resolver = MockDnsResolver::that_fails(); + let cached_address = "127.0.0.1".parse().unwrap(); + + let cache_file_path = write_address(&cache_dir, cached_address); + + make_file_old(&cache_file_path); + + let mut cache = create_cached_dns_resolver(mock_resolver, &cache_dir, None); + let address = cache.resolve(); + + assert_eq!(address, cached_address); + } + + #[test] fn caches_resolved_ip() { let (_temp_dir, cache_dir) = create_test_dirs(); let mock_address = "192.168.1.206".parse().unwrap(); @@ -197,6 +257,14 @@ mod tests { file_path } + fn make_file_old(file: &Path) { + let file_metadata = file.metadata().unwrap(); + let last_access_time = FileTime::from_last_access_time(&file_metadata); + let fake_modification_time = FileTime::from_seconds_since_1970(100_000, 0); + + filetime::set_file_times(&file, last_access_time, fake_modification_time).unwrap(); + } + fn get_cached_address(cache_dir: &Path) -> String { let cache_file_path = cache_dir.join("api_ip_address.txt"); |
