/*
* Copyright (c) 2026 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 Microsoft.Win32;
using ProtonVPN.Logging.Contracts;
using ProtonVPN.Logging.Contracts.Events.AppLogs;
namespace ProtonVPN.OperatingSystems.Network.NetworkInterfaces.Registries;
public class RegistryNetworkAdaptersProvider : IRegistryNetworkAdaptersProvider
{
private const string CLASS_PATH = @"SYSTEM\CurrentControlSet\Control\Class\{4d36e972-e325-11ce-bfc1-08002be10318}";
private readonly ILogger _logger;
public RegistryNetworkAdaptersProvider(ILogger logger)
{
_logger = logger;
}
public List Get()
{
List result = [];
try
{
using RegistryKey? adaptersKey = Registry.LocalMachine.OpenSubKey(CLASS_PATH);
if (adaptersKey == null)
{
return [];
}
foreach (string adapterSubKeyName in adaptersKey.GetSubKeyNames())
{
RegistryNetworkAdapter? adapter = EvaluateSubKey(adaptersKey, adapterSubKeyName);
if (adapter is not null)
{
result.Add(adapter);
}
}
}
catch (Exception ex)
{
_logger.Error("Exception when fetching network adapters from the registry.", ex);
return [];
}
return result;
}
private static RegistryNetworkAdapter? EvaluateSubKey(RegistryKey adaptersKey, string subKeyName)
{
// Subkeys are numbers (0000, 0001, ...)
if (!int.TryParse(subKeyName, out _))
{
return null;
}
using RegistryKey? adapterSubKey = adaptersKey.OpenSubKey(subKeyName);
if (adapterSubKey is null)
{
return null;
}
string? instanceId = ReadRegistryString(adapterSubKey, "NetCfgInstanceId");
string? serviceName = ReadRegistryString(adapterSubKey, "Service");
string? componentId = ReadRegistryString(adapterSubKey, "ComponentId");
string? driverDesc = ReadRegistryString(adapterSubKey, "DriverDesc");
string? providerName = ReadRegistryString(adapterSubKey, "ProviderName");
string? driverFileName = ReadRegistryString(adapterSubKey, "DriverFileName");
if (string.IsNullOrEmpty(driverFileName))
{
using RegistryKey? ndiSubKey = adapterSubKey.OpenSubKey("Ndi");
if (ndiSubKey is not null)
{
string? ndiService = ReadRegistryString(ndiSubKey, "Service");
driverFileName = GetDriverFileNameFromService(ndiService);
if (string.IsNullOrEmpty(serviceName))
{
serviceName = ndiService;
}
}
}
if (string.IsNullOrEmpty(driverFileName))
{
driverFileName = GetDriverFileNameFromService(serviceName);
}
return new RegistryNetworkAdapter
{
NetCfgInstanceId = instanceId,
ServiceName = serviceName,
ComponentId = componentId,
DriverDescription = driverDesc,
DriverProvider = providerName,
DriverFileName = driverFileName,
};
}
private static string? ReadRegistryString(RegistryKey key, string name)
{
object? value = key.GetValue(name);
return value is null ? null : value as string;
}
private static string? GetDriverFileNameFromService(string? serviceName)
{
if (string.IsNullOrWhiteSpace(serviceName))
{
return null;
}
string keyPath = $@"SYSTEM\CurrentControlSet\Services\{serviceName}";
using RegistryKey? serviceSubKey = Registry.LocalMachine.OpenSubKey(keyPath);
if (serviceSubKey is null)
{
return null;
}
string? imagePath = ReadRegistryString(serviceSubKey, "ImagePath");
if (string.IsNullOrWhiteSpace(imagePath))
{
return null;
}
try
{
return Path.GetFileName(imagePath);
}
catch
{
return imagePath;
}
}
}