/*
* Copyright (c) 2025 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.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using ProtonVPN.Common.Core.Networking;
using ProtonVPN.Configurations.Contracts;
using ProtonVPN.IPv6.Contracts;
using ProtonVPN.IPv6.Interop;
using ProtonVPN.Logging.Contracts;
using ProtonVPN.Logging.Contracts.Events.IPv6Logs;
namespace ProtonVPN.IPv6;
public class FakeIPv6AddressGenerator : IFakeIPv6AddressGenerator
{
private readonly ILogger _logger;
private readonly IConfiguration _config;
public FakeIPv6AddressGenerator(ILogger logger, IConfiguration config)
{
_logger = logger;
_config = config;
}
public async Task> GenerateAddressesAsync(
List prefixes,
List realIpv6Addresses,
ushort addressesToGenerate)
{
byte[]? prefixTree = await GetPrefixTreeAsync();
if (prefixTree == null)
{
return [];
}
string? persistedData = await GetPersistedDataAsync();
try
{
NativeResult result = (NativeResult)NativeMethods.CreateInstance(
[.. prefixes],
(nuint)prefixes.Count,
prefixTree,
(nuint)prefixTree.Length,
out Ipv6ChaosAlgoHandle handle);
using (handle)
{
if (result != NativeResult.Success)
{
_logger.Error("Failed to initialize IPv6 chaos instance.");
return [];
}
List addresses = GenerateAddressesInner(handle, persistedData, realIpv6Addresses, addressesToGenerate);
await PersistUpdatedDataAsync(handle, _config.IPv6PersistedDataFilePath);
_logger.Info($"Generated {addresses.Count} fake IPv6 addresses.");
return addresses;
}
}
catch (Exception e)
{
_logger.Error("Failed to generate fake IPv6 addresses list.", e);
return [];
}
}
private async Task GetPrefixTreeAsync()
{
if (!File.Exists(_config.IPv6PrefixTreeFilePath))
{
_logger.Error($"File {_config.IPv6PrefixTreeFilePath} does not exist.");
return null;
}
try
{
return await File.ReadAllBytesAsync(_config.IPv6PrefixTreeFilePath);
}
catch (Exception e)
{
_logger.Error("Failed to read IPv6 prefix tree file.", e);
return null;
}
}
private List GenerateAddressesInner(
Ipv6ChaosAlgoHandle handle,
string? persistedData,
List realIpv6Addresses,
ushort addressesToGenerate)
{
if (persistedData != null)
{
NativeResult persistResult = (NativeResult)NativeMethods.PersistLoad(handle, persistedData);
if (persistResult != NativeResult.Success)
{
_logger.Error($"Failed to load persisted data. Error code: {persistResult}. " +
"Since this operation is not required the code will proceed.");
}
}
NativeResult result = (NativeResult)NativeMethods.Generate(
handle,
[.. realIpv6Addresses],
(nuint)realIpv6Addresses.Count,
addressesToGenerate,
out FFISliceHandle sliceHandle);
using (sliceHandle)
{
if (result != NativeResult.Success || sliceHandle.IsInvalid)
{
_logger.Error($"Failed to generate fake IPv6 addresses. Error code: {result}");
return [];
}
FFISlice slice = Marshal.PtrToStructure(sliceHandle.DangerousGetHandle());
if (addressesToGenerate > 0 && slice.Len == 0)
{
_logger.Error($"Failed to generate fake IPv6 addresses. " +
$"Expected to get {addressesToGenerate}, but instead got {slice.Len}.");
return [];
}
return GetFakeIpv6Addresses(slice);
}
}
private async Task GetPersistedDataAsync()
{
if (!File.Exists(_config.IPv6PersistedDataFilePath))
{
_logger.Error($"File {_config.IPv6PersistedDataFilePath} does not exist.");
return null;
}
try
{
return await File.ReadAllTextAsync(_config.IPv6PersistedDataFilePath);
}
catch (Exception e)
{
_logger.Error("Failed to read persisted data file.", e);
return null;
}
}
private unsafe List GetFakeIpv6Addresses(FFISlice result)
{
int n = checked((int)result.Len);
List addresses = [];
ReadOnlySpan rawSpan = new(result.Ptr.ToPointer(), n);
for (int i = 0; i < n; i++)
{
byte[] buffer = new byte[16];
fixed (byte* src = rawSpan[i].Bytes)
{
new ReadOnlySpan(src, 16).CopyTo(buffer);
}
string address = new IpAddress { Bytes = buffer }.ToString() ?? string.Empty;
if (NetworkAddress.TryParse(address, out NetworkAddress networkAddress))
{
addresses.Add(networkAddress);
}
}
return addresses;
}
private async Task PersistUpdatedDataAsync(Ipv6ChaosAlgoHandle handle, string path)
{
NativeResult result = (NativeResult)NativeMethods.PersistDump(handle, out Utf8StringHandle stringHandle);
using (stringHandle)
{
if (result != NativeResult.Success)
{
return;
}
string persistedData = Marshal.PtrToStringUTF8(stringHandle.DangerousGetHandle()) ?? string.Empty;
if (!string.IsNullOrEmpty(persistedData))
{
await File.WriteAllTextAsync(path, persistedData);
}
}
}
}