diff options
| author | David Lönnhager <david.l@mullvad.net> | 2020-04-24 16:52:30 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2020-04-24 16:52:30 +0200 |
| commit | b41a0b2225411698955b6f0243c0a47eaeea0a02 (patch) | |
| tree | 1b871fc85ad18cf9786509bfa392447bfaf3815e | |
| parent | 874c98fa9e8103696c3f74068441bd0f71b7997e (diff) | |
| parent | 6419301639d5a87cea0c50aa13c9eff4e522e862 (diff) | |
| download | mullvadvpn-b41a0b2225411698955b6f0243c0a47eaeea0a02.tar.xz mullvadvpn-b41a0b2225411698955b6f0243c0a47eaeea0a02.zip | |
Merge branch 'fix-ipv6-issues'
| -rw-r--r-- | CHANGELOG.md | 4 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/mod.rs | 69 | ||||
| -rw-r--r-- | talpid-core/src/winnet.rs | 91 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/netconfig.cpp | 269 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/netconfig.h | 5 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/winnet.cpp | 88 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/winnet.def | 3 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/winnet.h | 23 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/winnet.vcxproj | 2 | ||||
| -rw-r--r-- | windows/winnet/src/winnet/winnet.vcxproj.filters | 2 |
10 files changed, 476 insertions, 80 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index f5160a390f..b66b2e3bbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,12 +29,16 @@ Line wrap the file at 100 chars. Th ### Changed - Downgrade to Electron 7 due to issues with tray icon in Electron 8. +#### Windows +- When required, attempt to enable IPv6 for network adapters instead of failing. + ### Fixed - Enable IPv6 in WireGuard regardless of the specified MTU value, previously IPv6 was disabled if the MTU was below 1380. #### Windows - Improve offline detection logic. +- Enable missing IPv6 interface on the WireGuard TUN adapter when it has been disabled. #### Android - Change button colors on problem report no email confirmation dialog to match the desktop version. diff --git a/talpid-core/src/tunnel/mod.rs b/talpid-core/src/tunnel/mod.rs index 22da520ce0..6881b02311 100644 --- a/talpid-core/src/tunnel/mod.rs +++ b/talpid-core/src/tunnel/mod.rs @@ -10,6 +10,8 @@ use std::{ #[cfg(not(target_os = "android"))] use talpid_types::net::openvpn as openvpn_types; use talpid_types::net::{wireguard as wireguard_types, TunnelParameters}; +#[cfg(target_os = "windows")] +use talpid_types::ErrorExt; /// A module for all OpenVPN related tunnel management. #[cfg(not(target_os = "android"))] @@ -207,21 +209,38 @@ impl TunnelMonitor { fn ensure_ipv6_can_be_used_if_enabled(tunnel_parameters: &TunnelParameters) -> Result<()> { let options = tunnel_parameters.get_generic_options(); - if options.enable_ipv6 { - #[cfg(target_os = "windows")] - let enabled = match tunnel_parameters { - TunnelParameters::OpenVpn(_) => is_ipv6_enabled_in_os(true), - _ => is_ipv6_enabled_in_os(false), - }?; - #[cfg(not(target_os = "windows"))] - let enabled = is_ipv6_enabled_in_os()?; - if enabled { + + #[cfg(target_os = "windows")] + match tunnel_parameters { + TunnelParameters::OpenVpn(..) => { + if options.enable_ipv6 { + try_enabling_ipv6(tunnel_parameters) + } else { + Ok(()) + } + } + TunnelParameters::Wireguard(..) => { + // WireGuard always waits on an IPv6 interface, + // even if it's not in use + if let Err(e) = try_enabling_ipv6(tunnel_parameters) { + log::error!("{}", e.display_chain_with_msg("Failed to enable IPv6")); + } Ok(()) + } + } + + #[cfg(not(target_os = "windows"))] + { + if options.enable_ipv6 { + let enabled = is_ipv6_enabled_in_os()?; + if enabled { + Ok(()) + } else { + Err(Error::EnableIpv6Error) + } } else { - Err(Error::EnableIpv6Error) + Ok(()) } - } else { - Ok(()) } } @@ -327,7 +346,7 @@ impl InternalTunnelMonitor { #[cfg(target_os = "windows")] -fn is_ipv6_enabled_in_os(check_tap: bool) -> Result<bool> { +fn try_enabling_ipv6(tunnel_parameters: &TunnelParameters) -> Result<()> { use winreg::{enums::*, RegKey}; const IPV6_DISABLED_ON_TUNNELS_MASK: u32 = 0x01; @@ -341,16 +360,24 @@ fn is_ipv6_enabled_in_os(check_tap: bool) -> Result<bool> { .unwrap_or(true); if !globally_enabled { - log::debug!("IPv6 disabled in tunnel interfaces"); + // TODO: Try to globally enable IPv6 + log::debug!("IPv6 disabled in all tunnel interfaces"); + return Err(Error::EnableIpv6Error); } - if check_tap { - let enabled_on_tap = - crate::winnet::get_tap_interface_ipv6_status().map_err(Error::WinnetError)?; - Ok(globally_enabled && enabled_on_tap) - } else { - Ok(globally_enabled) - } + let guid_string: String; + + let guid = match tunnel_parameters { + TunnelParameters::OpenVpn(..) => { + let alias = crate::winnet::get_tap_interface_alias().map_err(Error::WinnetError)?; + guid_string = + crate::winnet::interface_alias_to_guid(&alias).map_err(Error::WinnetError)?; + &guid_string + } + TunnelParameters::Wireguard(..) => "{AFE43773-E1F8-4EBB-8536-576AB86AFE9A}", + }; + + crate::winnet::enable_ipv6_for_adapter(&guid).map_err(Error::WinnetError) } #[cfg(not(target_os = "windows"))] diff --git a/talpid-core/src/winnet.rs b/talpid-core/src/winnet.rs index 21b5bb3ae7..a0b183d779 100644 --- a/talpid-core/src/winnet.rs +++ b/talpid-core/src/winnet.rs @@ -3,7 +3,11 @@ pub use self::api::{WinNet_ActivateConnectivityMonitor, WinNet_DeactivateConnect use crate::{logging::windows::log_sink, routing::Node}; use ipnetwork::IpNetwork; use libc::{c_void, wchar_t}; -use std::{ffi::OsString, net::IpAddr, ptr}; +use std::{ + ffi::{OsStr, OsString}, + net::IpAddr, + ptr, +}; use widestring::WideCString; /// Errors that this module may produce. @@ -17,6 +21,14 @@ pub enum Error { #[error(display = "Supplied interface alias is invalid")] InvalidInterfaceAlias(#[error(source)] widestring::NulError<u16>), + /// Failed to enable IPv6 on the network interface. + #[error(display = "Failed to enable IPv6 on the network interface")] + EnableIpv6, + + /// Failed to enable IPv6 on the network interface. + #[error(display = "Failed to obtain GUID for the network interface")] + GetInterfaceGuid, + /// Failed to read IPv6 status on the TAP network interface. #[error(display = "Failed to read IPv6 status on the TAP network interface")] GetIpv6Status, @@ -62,29 +74,23 @@ pub fn ensure_best_metric_for_interface(interface_alias: &str) -> Result<bool, E } } -/// Checks if IPv6 is enabled for the TAP interface -pub fn get_tap_interface_ipv6_status() -> Result<bool, Error> { - // WinNet_GetTapInterfaceIpv6Status() will fail if the alias cannot be retrieved. - // Try to retrieve it first so that we may return a more specific error. - let _ = get_tap_interface_alias()?; - let tap_ipv6_status = - unsafe { WinNet_GetTapInterfaceIpv6Status(Some(log_sink), logging_context()) }; +/// Enables IPv6 for a given interface. +pub fn enable_ipv6_for_adapter(interface_guid: &str) -> Result<(), Error> { + let interface_guid_ws = + WideCString::from_str(interface_guid).map_err(Error::InvalidInterfaceAlias)?; - match tap_ipv6_status { - // Enabled - 0 => Ok(true), - // Disabled - 1 => Ok(false), - // Failure - 2 => Err(Error::GetIpv6Status), - // Unexpected value - i => { - log::error!( - "Unexpected return code from WinNet_GetTapInterfaceIpv6Status: {}", - i - ); - Err(Error::GetIpv6Status) - } + let result = unsafe { + WinNet_EnableIpv6ForAdapter( + interface_guid_ws.as_ptr(), + Some(log_sink), + logging_context(), + ) + }; + + if result { + Ok(()) + } else { + Err(Error::EnableIpv6) } } @@ -105,6 +111,30 @@ pub fn get_tap_interface_alias() -> Result<OsString, Error> { Ok(alias.to_os_string()) } +/// Determines the interface guid for a given adapter alias. +pub fn interface_alias_to_guid(interface_alias: &OsStr) -> Result<String, Error> { + let interface_alias = + WideCString::from_os_str(interface_alias).map_err(Error::InvalidInterfaceAlias)?; + let mut guid_ptr: *mut wchar_t = ptr::null_mut(); + let status = unsafe { + WinNet_InterfaceAliasToGuid( + interface_alias.as_ptr(), + &mut guid_ptr as *mut _, + Some(log_sink), + logging_context(), + ) + }; + + if !status { + return Err(Error::GetInterfaceGuid); + } + + let guid = unsafe { WideCString::from_ptr_str(guid_ptr) }; + unsafe { WinNet_ReleaseString(guid_ptr) }; + + Ok(guid.to_string_lossy()) +} + #[allow(dead_code)] #[repr(u32)] pub enum WinNetAddrFamily { @@ -380,11 +410,12 @@ mod api { sink_context: *const u8, ) -> u32; - #[link_name = "WinNet_GetTapInterfaceIpv6Status"] - pub fn WinNet_GetTapInterfaceIpv6Status( + #[link_name = "WinNet_EnableIpv6ForAdapter"] + pub fn WinNet_EnableIpv6ForAdapter( + tunnel_interface_alias: *const wchar_t, sink: Option<LogSink>, sink_context: *const u8, - ) -> u32; + ) -> bool; #[link_name = "WinNet_GetTapInterfaceAlias"] pub fn WinNet_GetTapInterfaceAlias( @@ -393,6 +424,14 @@ mod api { sink_context: *const u8, ) -> bool; + #[link_name = "WinNet_InterfaceAliasToGuid"] + pub fn WinNet_InterfaceAliasToGuid( + interface_alias: *const wchar_t, + interface_guid: *mut *mut wchar_t, + sink: Option<LogSink>, + sink_context: *const u8, + ) -> bool; + #[link_name = "WinNet_ReleaseString"] pub fn WinNet_ReleaseString(string: *mut wchar_t); diff --git a/windows/winnet/src/winnet/netconfig.cpp b/windows/winnet/src/winnet/netconfig.cpp new file mode 100644 index 0000000000..7fd7b38869 --- /dev/null +++ b/windows/winnet/src/winnet/netconfig.cpp @@ -0,0 +1,269 @@ +#include "stdafx.h" +#include "netconfig.h" +#include <stdexcept> +#include <sstream> +#include <windows.h> +#include <netcfgx.h> +#include <devguid.h> +#include <libcommon/registry/registry.h> +#include <libcommon/error.h> +#include <libcommon/string.h> +#include <libcommon/memory.h> +#include <libshared/network/interfaceutils.h> + + +namespace +{ + +const wchar_t NETCFG_LOCK_CLIENT_NAME[] = L"MULLVAD"; +constexpr uint16_t NETCFG_LOCK_TIMEOUT = 5000; // milliseconds +const wchar_t NETCFG_IPV6_COMPONENT_NAME[] = L"MS_TCPIP6"; + +void SetIpv6BindingForBindName(INetCfg *netCfg, const std::wstring &bindName, bool enable) +{ + INetCfgComponent *transactionComponent = nullptr; + HRESULT result = netCfg->FindComponent(NETCFG_IPV6_COMPONENT_NAME, &transactionComponent); + + if (S_OK != result) + { + THROW_ERROR("Failed to obtain transaction component"); + } + + INetCfgComponentBindings *bindings = nullptr; + result = transactionComponent->QueryInterface( + IID_INetCfgComponentBindings, + reinterpret_cast<void**>(&bindings) + ); + + transactionComponent->Release(); + transactionComponent = nullptr; + + if (S_OK != result) + { + std::wstringstream ss; + ss << L"Failed to obtain component bindings for "; + ss << NETCFG_IPV6_COMPONENT_NAME; + THROW_ERROR(common::string::ToAnsi(ss.str()).c_str()); + } + + IEnumNetCfgBindingPath *pathsEnum = NULL; + result = bindings->EnumBindingPaths(EBP_BELOW, &pathsEnum); + + bindings->Release(); + bindings = nullptr; + + if (S_OK != result) + { + THROW_ERROR("Failed to acquire binding path enumerator"); + } + + common::memory::ScopeDestructor pathsEnumDestructor; + pathsEnumDestructor += [&pathsEnum]() { + pathsEnum->Release(); + pathsEnum = nullptr; + }; + + INetCfgBindingPath *bindingPath = NULL; + + result = pathsEnum->Next(1, &bindingPath, nullptr); + + for (; S_OK == result; result = pathsEnum->Next(1, &bindingPath, nullptr)) + { + common::memory::ScopeDestructor bindingPathDestructor; + bindingPathDestructor += [&bindingPath]() { + bindingPath->Release(); + bindingPath = nullptr; + }; + + IEnumNetCfgBindingInterface *enumInterface = nullptr; + HRESULT enumResult = bindingPath->EnumBindingInterfaces(&enumInterface); + + if (S_OK != enumResult) + { + THROW_ERROR("Failed to acquire binding path interfaces"); + } + + common::memory::ScopeDestructor interfaceEnumDestructor; + interfaceEnumDestructor += [&enumInterface]() { + enumInterface->Release(); + enumInterface = nullptr; + }; + + INetCfgBindingInterface *iface = nullptr; + + while (S_OK == enumInterface->Next(1, &iface, nullptr)) + { + INetCfgComponent *cfgComponent = nullptr; + + auto status = iface->GetLowerComponent(&cfgComponent); + + iface->Release(); + iface = nullptr; + + if (S_OK != status) + { + THROW_ERROR("Failed to acquire binding interface component"); + } + + wchar_t *componentBindName = 0; + + status = cfgComponent->GetBindName(&componentBindName); + + cfgComponent->Release(); + cfgComponent = nullptr; + + if (S_OK != status) + { + THROW_ERROR("Failed to acquire bind name"); + } + + bool matchesBindName = (0 == _wcsicmp(bindName.c_str(), componentBindName)); + CoTaskMemFree(componentBindName); + + if (matchesBindName) + { + // + // Apply the changes and exit the function + // + + result = bindingPath->Enable(enable); + if (S_OK != result) + { + THROW_ERROR("Failed to set IPv6 status"); + } + netCfg->Apply(); + + return; + } + } + } +} + +} // anonymous namespace + + +void EnableIpv6ForAdapter(const std::wstring &adapterGuid) +{ + try + { + // + // Avoid using the COM objects unless necessary + // due to slow performance. + // + + const auto key = common::registry::Registry::OpenKey( + HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Linkage" + ); + const auto bindings = key->readStringArray(L"Bind"); + + std::wstring matchString = std::wstring(L"\\Device\\").append(adapterGuid); + + for (auto it = bindings.begin(); it != bindings.end(); ++it) + { + if (0 == _wcsicmp(it->c_str(), matchString.c_str())) + { + // return from function + return; + } + } + } + catch (...) + { + } + + // + // Initialize COM + // + + HRESULT result = CoInitialize(nullptr); + + if (S_OK != result) + { + std::stringstream ss; + ss << "Failed to initialize COM: " << result; + THROW_ERROR(ss.str().c_str()); + } + + common::memory::ScopeDestructor scopeDest; + scopeDest += []() { + CoUninitialize(); + }; + + // + // Initialize INetCfg + // + + INetCfg *netCfg = nullptr; + result = CoCreateInstance( + CLSID_CNetCfg, + nullptr, + CLSCTX_INPROC_SERVER, + IID_INetCfg, + reinterpret_cast<void **>(&netCfg) + ); + + if (S_OK != result) + { + std::stringstream ss; + ss << "Failed to create INetCfg instance: " << result; + THROW_ERROR(ss.str().c_str()); + + } + + scopeDest += [&netCfg]() { netCfg->Release(); }; + + INetCfgLock *netCfgLock = nullptr; + result = netCfg->QueryInterface(IID_INetCfgLock, reinterpret_cast<void **>(&netCfgLock)); + + if (S_OK != result) + { + std::stringstream ss; + ss << "Failed to obtain INetCfg lock interface: " << result; + THROW_ERROR(ss.str().c_str()); + } + + scopeDest += [&netCfgLock]() { + netCfgLock->Release(); + }; + + wchar_t *blockingApplication = nullptr; + + // NOTE: This should be done before initializing INetCfg + result = netCfgLock->AcquireWriteLock( + NETCFG_LOCK_TIMEOUT, + NETCFG_LOCK_CLIENT_NAME, + &blockingApplication + ); + + if (S_OK != result) + { + std::wstringstream ss; + ss << L"Failed to acquire write lock"; + if (nullptr != blockingApplication) + { + ss << L" due to application: " << blockingApplication; + } + ss << ". (" << result << ")"; + + THROW_ERROR(common::string::ToAnsi(ss.str()).c_str()); + } + + scopeDest += [&]() { + CoTaskMemFree(blockingApplication); + netCfgLock->ReleaseWriteLock(); + }; + + result = netCfg->Initialize(nullptr); + + if (S_OK != result) + { + std::stringstream ss; + ss << "Failed to initialize INetCfg: " << result; + THROW_ERROR(ss.str().c_str()); + } + + scopeDest += [&netCfg]() { netCfg->Uninitialize(); }; + + SetIpv6BindingForBindName(netCfg, adapterGuid, true); +} diff --git a/windows/winnet/src/winnet/netconfig.h b/windows/winnet/src/winnet/netconfig.h new file mode 100644 index 0000000000..b2849857a2 --- /dev/null +++ b/windows/winnet/src/winnet/netconfig.h @@ -0,0 +1,5 @@ +#pragma once + +#include <string> + +void EnableIpv6ForAdapter(const std::wstring &guid); diff --git a/windows/winnet/src/winnet/winnet.cpp b/windows/winnet/src/winnet/winnet.cpp index 2a96d150ca..ac56e94ff6 100644 --- a/windows/winnet/src/winnet/winnet.cpp +++ b/windows/winnet/src/winnet/winnet.cpp @@ -4,6 +4,7 @@ #include "offlinemonitor.h"
#include "routing/routemanager.h"
#include "converters.h"
+#include "netconfig.h"
#include <libshared/logging/logsinkadapter.h>
#include <libshared/logging/unwind.h>
#include <libshared/network/interfaceutils.h>
@@ -65,43 +66,30 @@ WinNet_EnsureBestMetric( extern "C"
WINNET_LINKAGE
-WINNET_GTII_STATUS
+bool
WINNET_API
-WinNet_GetTapInterfaceIpv6Status(
+WinNet_EnableIpv6ForAdapter(
+ const wchar_t *deviceGuid,
MullvadLogSink logSink,
void *logSinkContext
)
{
try
{
- MIB_IPINTERFACE_ROW iface = { 0 };
-
- iface.InterfaceLuid = NetworkInterfaces::GetInterfaceLuid(InterfaceUtils::GetTapInterfaceAlias());
- iface.Family = AF_INET6;
-
- const auto status = GetIpInterfaceEntry(&iface);
-
- if (NO_ERROR == status)
- {
- return WINNET_GTII_STATUS_ENABLED;
- }
-
- if (ERROR_NOT_FOUND == status)
+ if (nullptr == deviceGuid)
{
- return WINNET_GTII_STATUS_DISABLED;
+ THROW_ERROR("Invalid argument: deviceGuid");
}
- THROW_WINDOWS_ERROR(status, "Resolve TAP IPv6 interface");
+ EnableIpv6ForAdapter(deviceGuid);
+ return true;
}
- catch (const std::exception &err)
+ catch (const std::exception & err)
{
shared::logging::UnwindAndLog(logSink, logSinkContext, err);
- return WINNET_GTII_STATUS_FAILURE;
- }
- catch (...)
- {
- return WINNET_GTII_STATUS_FAILURE;
+ return false;
}
+ return false;
}
extern "C"
@@ -143,6 +131,60 @@ WinNet_GetTapInterfaceAlias( extern "C"
WINNET_LINKAGE
+bool
+WINNET_API
+WinNet_InterfaceAliasToGuid(
+ const wchar_t *alias,
+ wchar_t **guid,
+ MullvadLogSink logSink,
+ void *logSinkContext
+)
+{
+ try
+ {
+ if (nullptr == guid)
+ {
+ THROW_ERROR("Invalid argument: guid");
+ }
+ if (nullptr == alias)
+ {
+ THROW_ERROR("Invalid argument: alias");
+ }
+
+ GUID tempGuid = { 0 };
+ NET_LUID luid = { 0 };
+
+ if (NO_ERROR != ConvertInterfaceAliasToLuid(alias, &luid))
+ {
+ THROW_ERROR("ConvertInterfaceAliasToLuid: invalid parameter");
+ }
+
+ if (NO_ERROR != ConvertInterfaceLuidToGuid(&luid, &tempGuid))
+ {
+ THROW_ERROR("ConvertInterfaceLuidToGuid: invalid parameter");
+ }
+
+ const auto guidStr = common::string::FormatGuid(tempGuid);
+
+ auto guidBuffer = new wchar_t[guidStr.size() + 1];
+ wcscpy(guidBuffer, guidStr.c_str());
+ *guid = guidBuffer;
+
+ return true;
+ }
+ catch (const std::exception & err)
+ {
+ shared::logging::UnwindAndLog(logSink, logSinkContext, err);
+ return false;
+ }
+ catch (...)
+ {
+ return false;
+ }
+}
+
+extern "C"
+WINNET_LINKAGE
void
WINNET_API
WinNet_ReleaseString(
diff --git a/windows/winnet/src/winnet/winnet.def b/windows/winnet/src/winnet/winnet.def index ecec97959e..5a1bbfe99f 100644 --- a/windows/winnet/src/winnet/winnet.def +++ b/windows/winnet/src/winnet/winnet.def @@ -1,7 +1,8 @@ LIBRARY winnet EXPORTS WinNet_EnsureBestMetric - WinNet_GetTapInterfaceIpv6Status + WinNet_InterfaceAliasToGuid + WinNet_EnableIpv6ForAdapter WinNet_GetTapInterfaceAlias WinNet_ReleaseString WinNet_ActivateConnectivityMonitor diff --git a/windows/winnet/src/winnet/winnet.h b/windows/winnet/src/winnet/winnet.h index 5e2a4154a4..98b0083f03 100644 --- a/windows/winnet/src/winnet/winnet.h +++ b/windows/winnet/src/winnet/winnet.h @@ -33,18 +33,12 @@ WinNet_EnsureBestMetric( void *logSinkContext ); -enum WINNET_GTII_STATUS -{ - WINNET_GTII_STATUS_ENABLED = 0, - WINNET_GTII_STATUS_DISABLED = 1, - WINNET_GTII_STATUS_FAILURE = 2, -}; - extern "C" WINNET_LINKAGE -WINNET_GTII_STATUS +bool WINNET_API -WinNet_GetTapInterfaceIpv6Status( +WinNet_EnableIpv6ForAdapter( + const wchar_t *deviceGuid, MullvadLogSink logSink, void *logSinkContext ); @@ -59,6 +53,17 @@ WinNet_GetTapInterfaceAlias( void *logSinkContext ); +extern "C" +WINNET_LINKAGE +bool +WINNET_API +WinNet_InterfaceAliasToGuid( + const wchar_t *alias, + wchar_t **guid, + MullvadLogSink logSink, + void *logSinkContext +); + // // This is a companion function to the above function. // Generically named in case we need other functions here that return strings. diff --git a/windows/winnet/src/winnet/winnet.vcxproj b/windows/winnet/src/winnet/winnet.vcxproj index 7b4578d4b1..6876035160 100644 --- a/windows/winnet/src/winnet/winnet.vcxproj +++ b/windows/winnet/src/winnet/winnet.vcxproj @@ -28,6 +28,7 @@ </ItemGroup> <ItemGroup> <ClCompile Include="converters.cpp" /> + <ClCompile Include="netconfig.cpp" /> <ClCompile Include="networkadaptermonitor.cpp" /> <ClCompile Include="dllmain.cpp" /> <ClCompile Include="InterfacePair.cpp" /> @@ -42,6 +43,7 @@ </ItemGroup> <ItemGroup> <ClInclude Include="converters.h" /> + <ClInclude Include="netconfig.h" /> <ClInclude Include="networkadaptermonitor.h" /> <ClInclude Include="InterfacePair.h" /> <ClInclude Include="offlinemonitor.h" /> diff --git a/windows/winnet/src/winnet/winnet.vcxproj.filters b/windows/winnet/src/winnet/winnet.vcxproj.filters index 2d5a039c6b..27f0051bc6 100644 --- a/windows/winnet/src/winnet/winnet.vcxproj.filters +++ b/windows/winnet/src/winnet/winnet.vcxproj.filters @@ -21,6 +21,7 @@ <Filter>routing</Filter> </ClCompile> <ClCompile Include="converters.cpp" /> + <ClCompile Include="netconfig.cpp" /> </ItemGroup> <ItemGroup> <ClInclude Include="stdafx.h" /> @@ -43,6 +44,7 @@ <Filter>routing</Filter> </ClInclude> <ClInclude Include="converters.h" /> + <ClInclude Include="netconfig.h" /> </ItemGroup> <ItemGroup> <None Include="winnet.def" /> |
