/* * Copyright (c) 2023 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using ProtonVPN.Client.Settings.Contracts; using ProtonVPN.Common.Core.Networking; using ProtonVPN.Dns.Caching; using ProtonVPN.Dns.Contracts; using ProtonVPN.Dns.Tests.Mocks; namespace ProtonVPN.Dns.Tests.Caching; [TestClass] public class DnsCacheManagerTest { public const int NUM_OF_PARALLEL_OPERATIONS = 100; private ISettings _settings; private MockOfLogger _logger; private DnsCacheManager _dnsCacheManager; [TestInitialize] public void TestInitialize() { _settings = Substitute.For(); _logger = new MockOfLogger(); _dnsCacheManager = new DnsCacheManager(_settings, _logger); } [TestCleanup] public void TestCleanup() { _settings = null; _logger = null; _dnsCacheManager = null; } [TestMethod] public async Task TestAddOrReplaceAsync_WhenCacheIsNull() { DnsResponse dnsResponse = new("host", TimeSpan.FromSeconds(12), new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); Assert.IsNull(_settings.DnsCache); await _dnsCacheManager.AddOrReplaceAsync(dnsResponse.Host, dnsResponse); Assert.IsNotNull(_settings.DnsCache); Assert.IsTrue(_settings.DnsCache.ContainsKey(dnsResponse.Host)); Assert.AreEqual(dnsResponse, _settings.DnsCache[dnsResponse.Host]); } [TestMethod] public async Task TestAddOrReplaceAsync_WhenCacheContainsSameHost() { DnsResponse dnsResponse = new("host", TimeSpan.FromSeconds(12), new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); DnsResponse cachedDnsResponse = new("host", TimeSpan.FromSeconds(13), new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); _settings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; await _dnsCacheManager.AddOrReplaceAsync(dnsResponse.Host, dnsResponse); Assert.IsNotNull(_settings.DnsCache); Assert.IsTrue(_settings.DnsCache.ContainsKey(dnsResponse.Host)); Assert.AreEqual(dnsResponse, _settings.DnsCache[dnsResponse.Host]); } [TestMethod] public async Task TestAddOrReplaceAsync_WhenCacheContainsDifferentHost() { DnsResponse dnsResponse = new("host12", TimeSpan.FromSeconds(12), new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); DnsResponse cachedDnsResponse = new("host13", TimeSpan.FromSeconds(13), new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); _settings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; await _dnsCacheManager.AddOrReplaceAsync(dnsResponse.Host, dnsResponse); Assert.IsNotNull(_settings.DnsCache); Assert.IsTrue(_settings.DnsCache.ContainsKey(dnsResponse.Host)); Assert.AreEqual(dnsResponse, _settings.DnsCache[dnsResponse.Host]); Assert.IsTrue(_settings.DnsCache.ContainsKey(cachedDnsResponse.Host)); Assert.AreEqual(cachedDnsResponse, _settings.DnsCache[cachedDnsResponse.Host]); } [TestMethod] public async Task TestAddOrReplaceAsync_WhenArgumentsAndCacheAreNull() { Assert.IsNull(_settings.DnsCache); await _dnsCacheManager.AddOrReplaceAsync(null, null); Assert.IsNull(_settings.DnsCache); } [TestMethod] public async Task TestAddOrReplaceAsync_WhenArgumentsAreNullAndCacheContainsDifferentHost() { DnsResponse cachedDnsResponse = new("host13", TimeSpan.FromSeconds(13), new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); _settings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; await _dnsCacheManager.AddOrReplaceAsync(null, null); Assert.IsNotNull(_settings.DnsCache); Assert.IsTrue(_settings.DnsCache.ContainsKey(cachedDnsResponse.Host)); Assert.AreEqual(cachedDnsResponse, _settings.DnsCache[cachedDnsResponse.Host]); } [TestMethod] public void TestAddOrReplaceAsync_ParallelWithDifferentHosts() { IList dnsResponses = new List(); for (int i = 0; i < NUM_OF_PARALLEL_OPERATIONS; i++) { dnsResponses.Add(new DnsResponse($"host{i}", TimeSpan.FromSeconds(100 + i), new List { new IpAddress(IPAddress.Parse($"192.168.{i}.{i}")) })); } Assert.IsNull(_settings.DnsCache); IList> tasks = new List>(); foreach (DnsResponse dnsResponse in dnsResponses) { tasks.Add(_dnsCacheManager.AddOrReplaceAsync(dnsResponse.Host, dnsResponse)); } Task.WaitAll(tasks.ToArray()); Assert.IsNotNull(_settings.DnsCache); Assert.HasCount(NUM_OF_PARALLEL_OPERATIONS, _settings.DnsCache); foreach (DnsResponse dnsResponse in dnsResponses) { Assert.IsTrue(_settings.DnsCache.ContainsKey(dnsResponse.Host)); Assert.AreEqual(dnsResponse, _settings.DnsCache[dnsResponse.Host]); } } [TestMethod] public void TestAddOrReplaceAsync_ParallelWithSameHost() { IList dnsResponses = new List(); for (int i = 0; i < NUM_OF_PARALLEL_OPERATIONS; i++) { dnsResponses.Add(new DnsResponse("host", TimeSpan.FromSeconds(100 + i), new List { new IpAddress(IPAddress.Parse($"192.168.{i}.{i}")) })); } Assert.IsNull(_settings.DnsCache); IList> tasks = new List>(); foreach (DnsResponse dnsResponse in dnsResponses) { tasks.Add(_dnsCacheManager.AddOrReplaceAsync(dnsResponse.Host, dnsResponse)); } Task.WaitAll(tasks.ToArray()); Assert.IsNotNull(_settings.DnsCache); Assert.HasCount(1, _settings.DnsCache); Assert.IsTrue(_settings.DnsCache.ContainsKey("host")); Assert.AreEqual("host", _settings.DnsCache["host"].Host); Assert.StartsWith("192.168.", _settings.DnsCache.Values.Single().IpAddresses.Single().ToString()); } [TestMethod] public async Task TestUpdateAsync_WhenCacheIsNull() { DnsResponse dnsResponse = new("host", TimeSpan.FromSeconds(12), new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); Assert.IsNull(_settings.DnsCache); await _dnsCacheManager.UpdateAsync(dnsResponse.Host, _ => dnsResponse); Assert.IsNull(_settings.DnsCache); } [TestMethod] public async Task TestUpdateAsync_WhenCacheContainsDifferentHost() { DnsResponse dnsResponse = new("host12", TimeSpan.FromSeconds(12), new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); DnsResponse cachedDnsResponse = new("host13", TimeSpan.FromSeconds(13), new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); _settings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; await _dnsCacheManager.UpdateAsync(dnsResponse.Host, _ => dnsResponse); Assert.IsNotNull(_settings.DnsCache); Assert.IsTrue(_settings.DnsCache.ContainsKey(cachedDnsResponse.Host)); Assert.AreEqual(cachedDnsResponse, _settings.DnsCache[cachedDnsResponse.Host]); Assert.IsFalse(_settings.DnsCache.ContainsKey(dnsResponse.Host)); } [TestMethod] public async Task TestUpdateAsync_WhenCacheContainsSameHost() { DnsResponse dnsResponse = new("host", TimeSpan.FromSeconds(12), new List { new IpAddress(IPAddress.Parse("192.168.12.12")) }); DnsResponse cachedDnsResponse = new("host", TimeSpan.FromSeconds(13), new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); _settings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; await _dnsCacheManager.UpdateAsync(dnsResponse.Host, _ => dnsResponse); Assert.IsNotNull(_settings.DnsCache); Assert.IsTrue(_settings.DnsCache.ContainsKey(dnsResponse.Host)); Assert.AreEqual(dnsResponse, _settings.DnsCache[dnsResponse.Host]); } [TestMethod] public async Task TestUpdateAsync_WhenArgumentsAndCacheAreNull() { Assert.IsNull(_settings.DnsCache); await _dnsCacheManager.UpdateAsync(null, null); Assert.IsNull(_settings.DnsCache); } [TestMethod] public async Task TestUpdateAsync_WhenArgumentsAreNullAndCacheContainsDifferentHost() { DnsResponse cachedDnsResponse = new("host13", TimeSpan.FromSeconds(13), new List { new IpAddress(IPAddress.Parse("192.168.13.13")) }); _settings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; await _dnsCacheManager.UpdateAsync(null, null); Assert.IsNotNull(_settings.DnsCache); Assert.IsTrue(_settings.DnsCache.ContainsKey(cachedDnsResponse.Host)); Assert.AreEqual(cachedDnsResponse, _settings.DnsCache[cachedDnsResponse.Host]); } [TestMethod] public void TestUpdateAsync_ParallelWithDifferentHosts() { _settings.DnsCache = new(); IList dnsResponses = new List(); for (int i = 0; i < NUM_OF_PARALLEL_OPERATIONS; i++) { DnsResponse cachedDnsResponse = new($"host{i}", TimeSpan.FromSeconds(1000 + i), new List { new IpAddress(IPAddress.Parse($"172.16.{i}.{i}")) }); _settings.DnsCache.TryAdd(cachedDnsResponse.Host, cachedDnsResponse); DnsResponse dnsResponse = new(cachedDnsResponse.Host, TimeSpan.FromSeconds(100 + i), new List { new IpAddress(IPAddress.Parse($"192.168.{i}.{i}")) }); dnsResponses.Add(dnsResponse); } Assert.HasCount(NUM_OF_PARALLEL_OPERATIONS, _settings.DnsCache); foreach (DnsResponse cachedDnsResponse in _settings.DnsCache.Values) { DnsResponse newDnsResponse = dnsResponses.Single(dr => dr.Host == cachedDnsResponse.Host); Assert.AreNotEqual(cachedDnsResponse, newDnsResponse); Assert.AreNotEqual(cachedDnsResponse.TimeToLive, newDnsResponse.TimeToLive); Assert.AreNotEqual(cachedDnsResponse.IpAddresses, newDnsResponse.IpAddresses); } IList> tasks = new List>(); foreach (DnsResponse dnsResponse in dnsResponses) { tasks.Add(_dnsCacheManager.UpdateAsync(dnsResponse.Host, _ => dnsResponse)); } Task.WaitAll(tasks.ToArray()); Assert.HasCount(NUM_OF_PARALLEL_OPERATIONS, _settings.DnsCache); foreach (DnsResponse dnsResponse in dnsResponses) { Assert.IsTrue(_settings.DnsCache.ContainsKey(dnsResponse.Host)); Assert.AreEqual(dnsResponse, _settings.DnsCache[dnsResponse.Host]); } } [TestMethod] public void TestUpdateAsync_ParallelWithSameHost() { DnsResponse cachedDnsResponse = new("host", TimeSpan.FromSeconds(13), new List { new IpAddress(IPAddress.Parse("172.16.1.1")) }); _settings.DnsCache = new() { [cachedDnsResponse.Host] = cachedDnsResponse }; IList dnsResponses = new List(); for (int i = 0; i < NUM_OF_PARALLEL_OPERATIONS; i++) { dnsResponses.Add(new DnsResponse("host", TimeSpan.FromSeconds(100 + i), new List { new IpAddress(IPAddress.Parse($"192.168.{i}.{i}")) })); } Assert.IsNotNull(_settings.DnsCache); Assert.HasCount(1, _settings.DnsCache); Assert.AreEqual(cachedDnsResponse, _settings.DnsCache.Values.Single()); IList> tasks = new List>(); foreach (DnsResponse dnsResponse in dnsResponses) { tasks.Add(_dnsCacheManager.UpdateAsync(dnsResponse.Host, _ => dnsResponse)); } Task.WaitAll(tasks.ToArray()); Assert.IsNotNull(_settings.DnsCache); Assert.HasCount(1, _settings.DnsCache); Assert.IsTrue(_settings.DnsCache.ContainsKey("host")); Assert.AreEqual("host", _settings.DnsCache["host"].Host); Assert.StartsWith("192.168.", _settings.DnsCache.Values.Single().IpAddresses.Single().ToString()); } }