virtionet: improve state tracking (#14215)

* virtionet: improve state tracking * remove unused var --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>

Ben Hillis committed Feb 13, 2026 at 12:10 UTC a27d4725f7fc980a98458035d803455edd1f50fa
2 files changed +137 -105
src/windows/common/VirtioNetworking.cpp
+131 -104
@@ -46,34 +46,19 @@ void VirtioNetworking::Initialize()
46 THROW_IF_WIN32_ERROR(NotifyNetworkConnectivityHintChange(&VirtioNetworking::OnNetworkConnectivityChange, this, TRUE, &m_networkNotifyHandle));
47 }
48
49 -void VirtioNetworking::SetupLoopbackDevice()
49 +void VirtioNetworking::TraceLoggingRundown() noexcept
50 {
51 - m_localhostAdapterId = m_guestDeviceManager->AddGuestDevice(
52 - VIRTIO_NET_DEVICE_ID,
53 - VIRTIO_NET_CLASS_ID,
54 - c_loopbackDeviceName,
55 - nullptr,
56 - L"client_ip=127.0.0.1;client_mac=00:11:22:33:44:55",
57 - 0,
58 - m_userToken.get());
51 + auto lock = m_lock.lock_exclusive();
52
60 - // The loopback gateway (see LX_INIT_IPV4_LOOPBACK_GATEWAY_ADDRESS) is 169.254.73.152, so assign loopback0 an
61 - // address of 169.254.73.153 with a netmask of 30 so that the only addresses associated with this adapter are
62 - // itself and the gateway.
63 - // N.B. The MAC address is advertised with the virtio device so doesn't need to be explicitly set.
64 - hns::HNSEndpoint endpointProperties;
65 - endpointProperties.ID = m_localhostAdapterId.value();
66 - endpointProperties.IPAddress = L"169.254.73.153";
67 - endpointProperties.PrefixLength = 30;
68 - endpointProperties.PortFriendlyName = c_loopbackDeviceName;
69 - m_gnsChannel.SendEndpointState(endpointProperties);
53 + WSL_LOG("VirtioNetworking::TraceLoggingRundown", TRACE_NETWORKSETTINGS_OBJECT(m_networkSettings));
54 +}
55
71 - hns::CreateDeviceRequest createLoopbackDevice;
72 - createLoopbackDevice.deviceName = c_loopbackDeviceName;
73 - createLoopbackDevice.type = hns::DeviceType::Loopback;
74 - createLoopbackDevice.lowerEdgeAdapterId = m_localhostAdapterId.value();
75 - constexpr auto loopbackType = GnsMessageType(createLoopbackDevice);
76 - m_gnsChannel.SendNetworkDeviceMessage(loopbackType, ToJsonW(createLoopbackDevice).c_str());
56 +void VirtioNetworking::FillInitialConfiguration(LX_MINI_INIT_NETWORKING_CONFIGURATION& message)
57 +{
58 + message.NetworkingMode = LxMiniInitNetworkingModeVirtioProxy;
59 + message.DisableIpv6 = false;
60 + message.EnableDhcpClient = false;
61 + message.PortTrackerType = LX_MINI_INIT_PORT_TRACKER_TYPE::LxMiniInitPortTrackerTypeMirrored;
62 }
63
64 void VirtioNetworking::StartPortTracker(wil::unique_socket&& socket)
@@ -86,6 +71,11 @@ void VirtioNetworking::StartPortTracker(wil::unique_socket&& socket)
71 [](const std::string&, bool) {}); // TODO: reconsider if InterfaceStateCallback is needed.
72 }
73
74 +void NETIOAPI_API_ VirtioNetworking::OnNetworkConnectivityChange(PVOID context, NL_NETWORK_CONNECTIVITY_HINT hint)
75 +{
76 + static_cast<VirtioNetworking*>(context)->RefreshGuestConnection();
77 +}
78 +
79 HRESULT VirtioNetworking::HandlePortNotification(const SOCKADDR_INET& addr, int protocol, bool allocate) const noexcept
80 {
81 int result = 0;
@@ -176,141 +166,178 @@ int VirtioNetworking::ModifyOpenPorts(_In_ PCWSTR tag, _In_ const SOCKADDR_INET&
166 return 0;
167 }
168
179 -void NETIOAPI_API_ VirtioNetworking::OnNetworkConnectivityChange(PVOID context, NL_NETWORK_CONNECTIVITY_HINT hint)
180 -{
181 - static_cast<VirtioNetworking*>(context)->RefreshGuestConnection();
182 -}
183 -
169 void VirtioNetworking::RefreshGuestConnection() noexcept
170 try
171 {
187 - // Acquire the lock and perform device updates.
188 - auto lock = m_lock.lock_exclusive();
189 -
190 - m_networkSettings = GetHostEndpointSettings();
172 + // Query current networking information before acquiring the lock.
173 + auto networkSettings = GetHostEndpointSettings();
174
175 // TODO: Determine gateway MAC address
193 - std::wstringstream device_options;
194 - auto client_ip = m_networkSettings->PreferredIpAddress.AddressString;
176 + std::wstring device_options;
177 + auto client_ip = networkSettings->PreferredIpAddress.AddressString;
178 if (!client_ip.empty())
179 {
197 - if (device_options.tellp() > 0)
198 - {
199 - device_options << L";";
200 - }
201 - device_options << L"client_ip=" << client_ip;
180 + device_options += L"client_ip=" + client_ip;
181 }
182
204 - if (!m_networkSettings->MacAddress.empty())
183 + if (!networkSettings->MacAddress.empty())
184 {
206 - if (device_options.tellp() > 0)
185 + if (!device_options.empty())
186 {
208 - device_options << L";";
187 + device_options += L';';
188 }
210 - device_options << L"client_mac=" << m_networkSettings->MacAddress;
189 + device_options += L"client_mac=" + networkSettings->MacAddress;
190 }
191
213 - std::wstring default_route = m_networkSettings->GetBestGatewayAddressString();
192 + std::wstring default_route = networkSettings->GetBestGatewayAddressString();
193 if (!default_route.empty())
194 {
216 - if (device_options.tellp() > 0)
195 + if (!device_options.empty())
196 {
218 - device_options << L";";
197 + device_options += L';';
198 }
220 - device_options << L"gateway_ip=" << default_route;
199 + device_options += L"gateway_ip=" + default_route;
200 }
201
223 - const auto newDeviceOptions = device_options.str();
224 -
225 - if (newDeviceOptions != m_trackedDeviceOptions)
202 + networking::DnsInfo currentDns{};
203 + if (WI_IsFlagSet(m_flags, VirtioNetworkingFlags::DnsTunneling))
204 + {
205 + currentDns = networking::HostDnsInfo::GetDnsTunnelingSettings(default_route);
206 + }
207 + else
208 {
227 - m_trackedDeviceOptions = newDeviceOptions;
209 + currentDns = networking::HostDnsInfo::GetDnsSettings(networking::DnsSettingsFlags::IncludeVpn);
210 + }
211 +
212 + const auto minMtu = GetMinimumConnectedInterfaceMtu();
213
229 - // Add virtio net adapter to guest. If the adapter already exists update adapter state.
214 + // Acquire the lock and perform device updates.
215 + auto lock = m_lock.lock_exclusive();
216 +
217 + // Add virtio net adapter to guest. If the adapter already exists update adapter state.
218 + if (device_options != m_trackedDeviceOptions)
219 + {
220 + m_trackedDeviceOptions = device_options;
221 if (!m_adapterId.has_value())
222 {
223 m_adapterId = m_guestDeviceManager->AddGuestDevice(
233 - VIRTIO_NET_DEVICE_ID, VIRTIO_NET_CLASS_ID, c_eth0DeviceName, nullptr, newDeviceOptions.c_str(), 0, m_userToken.get());
224 + VIRTIO_NET_DEVICE_ID, VIRTIO_NET_CLASS_ID, c_eth0DeviceName, nullptr, device_options.c_str(), 0, m_userToken.get());
225 }
226 else
227 {
228 const auto server = m_guestDeviceManager->GetRemoteFileSystem(VIRTIO_NET_CLASS_ID, c_defaultDeviceTag);
229 if (server)
230 {
240 - LOG_IF_FAILED(server->AddSharePath(c_eth0DeviceName, newDeviceOptions.c_str(), 0));
231 + LOG_IF_FAILED(server->AddSharePath(c_eth0DeviceName, device_options.c_str(), 0));
232 }
233 }
243 -
244 - // N.B. The MAC address is advertised with the virtio device so doesn't need to be explicitly set.
245 - hns::HNSEndpoint endpointProperties;
246 - endpointProperties.ID = m_adapterId.value();
247 - endpointProperties.IPAddress = m_networkSettings->PreferredIpAddress.AddressString;
248 - endpointProperties.PrefixLength = m_networkSettings->PreferredIpAddress.PrefixLength;
249 - m_gnsChannel.SendEndpointState(endpointProperties);
250 -
251 - // Send the default route to GNS.
252 - if (!default_route.empty())
253 - {
254 - wsl::shared::hns::Route route;
255 - route.NextHop = default_route;
256 - route.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_PREFIX;
257 - route.Family = AF_INET;
258 -
259 - hns::ModifyGuestEndpointSettingRequest<hns::Route> request;
260 - request.RequestType = hns::ModifyRequestType::Add;
261 - request.ResourceType = hns::GuestEndpointResourceType::Route;
262 - request.Settings = route;
263 - m_gnsChannel.SendHnsNotification(ToJsonW(request).c_str(), m_adapterId.value());
264 - }
234 }
235
267 - // Send DNS update if needed.
268 - networking::DnsInfo currentDns{};
269 - if (WI_IsFlagSet(m_flags, VirtioNetworkingFlags::DnsTunneling))
236 + // Update IP address if needed.
237 + if (!m_networkSettings || networkSettings->PreferredIpAddress != m_networkSettings->PreferredIpAddress)
238 {
271 - currentDns = networking::HostDnsInfo::GetDnsTunnelingSettings(default_route);
239 + UpdateIpAddress(networkSettings->PreferredIpAddress);
240 }
273 - else
241 +
242 + // Send default route update if needed.
243 + if (default_route != m_trackedDefaultRoute)
244 {
275 - currentDns = networking::HostDnsInfo::GetDnsSettings(networking::DnsSettingsFlags::IncludeVpn);
245 + m_trackedDefaultRoute = default_route;
246 + UpdateDefaultRoute(default_route, AF_INET);
247 }
248
249 + // Send DNS update if needed.
250 if (currentDns != m_trackedDnsSettings)
251 {
252 m_trackedDnsSettings = currentDns;
281 - hns::ModifyGuestEndpointSettingRequest<hns::DNS> notification{};
282 - notification.RequestType = hns::ModifyRequestType::Update;
283 - notification.ResourceType = hns::GuestEndpointResourceType::DNS;
284 - notification.Settings = networking::BuildDnsNotification(currentDns, m_dnsOptions);
285 - m_gnsChannel.SendHnsNotification(ToJsonW(notification).c_str(), m_adapterId.value());
253 + UpdateDnsSettings(currentDns);
254 }
255
256 // Send MTU update if needed.
289 - const auto minMtu = GetMinimumConnectedInterfaceMtu();
257 if (minMtu && minMtu.value() != m_networkMtu)
258 {
259 m_networkMtu = minMtu.value();
293 - hns::ModifyGuestEndpointSettingRequest<hns::NetworkInterface> notification{};
294 - notification.ResourceType = hns::GuestEndpointResourceType::Interface;
295 - notification.RequestType = hns::ModifyRequestType::Update;
296 - notification.Settings.Connected = true;
297 - notification.Settings.NlMtu = m_networkMtu;
298 - m_gnsChannel.SendHnsNotification(ToJsonW(notification).c_str(), m_adapterId.value());
260 + UpdateMtu(m_networkMtu);
261 }
262 +
263 + m_networkSettings = std::move(networkSettings);
264 }
265 CATCH_LOG();
266
303 -void VirtioNetworking::TraceLoggingRundown() noexcept
267 +void VirtioNetworking::SetupLoopbackDevice()
268 {
305 - auto lock = m_lock.lock_exclusive();
269 + m_localhostAdapterId = m_guestDeviceManager->AddGuestDevice(
270 + VIRTIO_NET_DEVICE_ID,
271 + VIRTIO_NET_CLASS_ID,
272 + c_loopbackDeviceName,
273 + nullptr,
274 + L"client_ip=127.0.0.1;client_mac=00:11:22:33:44:55",
275 + 0,
276 + m_userToken.get());
277
307 - WSL_LOG("VirtioNetworking::TraceLoggingRundown", TRACE_NETWORKSETTINGS_OBJECT(m_networkSettings));
278 + // The loopback gateway (see LX_INIT_IPV4_LOOPBACK_GATEWAY_ADDRESS) is 169.254.73.152, so assign loopback0 an
279 + // address of 169.254.73.153 with a netmask of 30 so that the only addresses associated with this adapter are
280 + // itself and the gateway.
281 + // N.B. The MAC address is advertised with the virtio device so doesn't need to be explicitly set.
282 + hns::HNSEndpoint endpointProperties;
283 + endpointProperties.ID = m_localhostAdapterId.value();
284 + endpointProperties.IPAddress = L"169.254.73.153";
285 + endpointProperties.PrefixLength = 30;
286 + endpointProperties.PortFriendlyName = c_loopbackDeviceName;
287 + m_gnsChannel.SendEndpointState(endpointProperties);
288 +
289 + hns::CreateDeviceRequest createLoopbackDevice;
290 + createLoopbackDevice.deviceName = c_loopbackDeviceName;
291 + createLoopbackDevice.type = hns::DeviceType::Loopback;
292 + createLoopbackDevice.lowerEdgeAdapterId = m_localhostAdapterId.value();
293 + constexpr auto loopbackType = GnsMessageType(createLoopbackDevice);
294 + m_gnsChannel.SendNetworkDeviceMessage(loopbackType, ToJsonW(createLoopbackDevice).c_str());
295 }
296
310 -void VirtioNetworking::FillInitialConfiguration(LX_MINI_INIT_NETWORKING_CONFIGURATION& message)
297 +void VirtioNetworking::UpdateDefaultRoute(const std::wstring& gateway, ADDRESS_FAMILY family)
298 {
312 - message.NetworkingMode = LxMiniInitNetworkingModeVirtioProxy;
313 - message.DisableIpv6 = false;
314 - message.EnableDhcpClient = false;
315 - message.PortTrackerType = LX_MINI_INIT_PORT_TRACKER_TYPE::LxMiniInitPortTrackerTypeMirrored;
299 + if (gateway.empty())
300 + {
301 + return;
302 + }
303 +
304 + wsl::shared::hns::Route route;
305 + route.NextHop = gateway;
306 + route.DestinationPrefix = (family == AF_INET) ? LX_INIT_DEFAULT_ROUTE_PREFIX : LX_INIT_DEFAULT_ROUTE_V6_PREFIX;
307 + route.Family = family;
308 +
309 + hns::ModifyGuestEndpointSettingRequest<hns::Route> request;
310 + request.RequestType = hns::ModifyRequestType::Add;
311 + request.ResourceType = hns::GuestEndpointResourceType::Route;
312 + request.Settings = route;
313 + m_gnsChannel.SendHnsNotification(ToJsonW(request).c_str(), m_adapterId.value());
314 +}
315 +
316 +void VirtioNetworking::UpdateDnsSettings(const networking::DnsInfo& dns)
317 +{
318 + hns::ModifyGuestEndpointSettingRequest<hns::DNS> notification{};
319 + notification.RequestType = hns::ModifyRequestType::Update;
320 + notification.ResourceType = hns::GuestEndpointResourceType::DNS;
321 + notification.Settings = networking::BuildDnsNotification(dns, m_dnsOptions);
322 + m_gnsChannel.SendHnsNotification(ToJsonW(notification).c_str(), m_adapterId.value());
323 +}
324 +
325 +void VirtioNetworking::UpdateIpAddress(const networking::EndpointIpAddress& ipAddress)
326 +{
327 + // N.B. The MAC address is advertised with the virtio device so doesn't need to be explicitly set.
328 + hns::HNSEndpoint endpointProperties;
329 + endpointProperties.ID = m_adapterId.value();
330 + endpointProperties.IPAddress = ipAddress.AddressString;
331 + endpointProperties.PrefixLength = ipAddress.PrefixLength;
332 + m_gnsChannel.SendEndpointState(endpointProperties);
333 +}
334 +
335 +void VirtioNetworking::UpdateMtu(ULONG mtu)
336 +{
337 + hns::ModifyGuestEndpointSettingRequest<hns::NetworkInterface> notification{};
338 + notification.ResourceType = hns::GuestEndpointResourceType::Interface;
339 + notification.RequestType = hns::ModifyRequestType::Update;
340 + notification.Settings.Connected = true;
341 + notification.Settings.NlMtu = mtu;
342 + m_gnsChannel.SendHnsNotification(ToJsonW(notification).c_str(), m_adapterId.value());
343 }
src/windows/common/VirtioNetworking.h
+6 -1
@@ -43,6 +43,10 @@ private:
43 int ModifyOpenPorts(_In_ PCWSTR tag, _In_ const SOCKADDR_INET& addr, _In_ int protocol, _In_ bool isOpen) const;
44 void RefreshGuestConnection() noexcept;
45 void SetupLoopbackDevice();
46 + void UpdateDefaultRoute(const std::wstring& gateway, ADDRESS_FAMILY family);
47 + void UpdateDnsSettings(const networking::DnsInfo& dns);
48 + void UpdateIpAddress(const networking::EndpointIpAddress& ipAddress);
49 + void UpdateMtu(ULONG mtu);
50
51 mutable wil::srwlock m_lock;
52
@@ -58,7 +62,8 @@ private:
62
63 ULONG m_networkMtu = 0;
64 std::wstring m_trackedDeviceOptions;
61 - networking::DnsInfo m_trackedDnsSettings;
65 + std::wstring m_trackedDefaultRoute;
66 + networking::DnsInfo m_trackedDnsSettings{};
67
68 // Note: this field must be destroyed first to stop the callbacks before any other field is destroyed.
69 networking::unique_notify_handle m_networkNotifyHandle;