| 1 | // Copyright (C) Microsoft Corporation. All rights reserved. |
| 2 | |
| 3 | #include "precomp.h" |
| 4 | |
| 5 | #include "WslCoreGuestNetworkService.h" |
| 6 | #include "WslCoreNetworkingSupport.h" |
| 7 | |
| 8 | #include <TraceLoggingProvider.h> |
| 9 | |
| 10 | #include "Stringify.h" |
| 11 | #include "WmiService.h" |
| 12 | #include "WslTelemetry.h" |
| 13 | #include "hns_schema.h" |
| 14 | |
| 15 | static constexpr auto c_computeNetworkModuleName = L"ComputeNetwork.dll"; |
| 16 | static constexpr auto c_dnsPortNumber = 53; |
| 17 | static constexpr auto c_mdnsPortNumber = 5353; |
| 18 | static constexpr auto c_llmnrPortNumber = 5355; |
| 19 | |
| 20 | static constexpr std::pair<uint16_t, uint16_t> c_invalidEphemeralPortRange = {1, 0}; |
| 21 | |
| 22 | using namespace wsl::shared; |
| 23 | |
| 24 | constexpr IN_ADDR c_ipv4LoopbackAddr = IN4ADDR_LOOPBACK_INIT; |
| 25 | |
| 26 | std::optional<LxssDynamicFunction<decltype(HcnReserveGuestNetworkServicePortRange)>> wsl::core::networking::GuestNetworkService::m_allocatePortRange; |
| 27 | std::optional<LxssDynamicFunction<decltype(HcnReserveGuestNetworkServicePort)>> wsl::core::networking::GuestNetworkService::m_allocatePort; |
| 28 | std::optional<LxssDynamicFunction<decltype(HcnReleaseGuestNetworkServicePortReservationHandle)>> wsl::core::networking::GuestNetworkService::m_releasePort; |
| 29 | |
| 30 | wsl::core::networking::GuestNetworkService::GuestNetworkService() noexcept |
| 31 | { |
| 32 | if (wsl::core::networking::IsFlowSteeringSupportedByHns()) |
| 33 | { |
| 34 | |
| 35 | static std::once_flag flag; |
| 36 | std::call_once(flag, [&]() { |
| 37 | try |
| 38 | { |
| 39 | m_allocatePortRange.emplace(c_computeNetworkModuleName, "HcnReserveGuestNetworkServicePortRange"); |
| 40 | m_allocatePort.emplace(c_computeNetworkModuleName, "HcnReserveGuestNetworkServicePort"); |
| 41 | m_releasePort.emplace(c_computeNetworkModuleName, "HcnReleaseGuestNetworkServicePortReservationHandle"); |
| 42 | } |
| 43 | CATCH_LOG() |
| 44 | }); |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | void wsl::core::networking::GuestNetworkService::CreateGuestNetworkService( |
| 49 | const bool firewallEnabled, const std::set<uint16_t>& IgnoredPorts, const GUID& VmId, const UUID& ServerUuid, HCN_NOTIFICATION_CALLBACK Callback, void* CallbackContext) |
| 50 | { |
| 51 | // we must first enable mirrored networking - which must by done by indirectly issuing a query with these special flags |
| 52 | wsl::core::networking::EnumerateMirroredNetworksAndHyperVFirewall(firewallEnabled); |
| 53 | |
| 54 | m_ignoredPorts = IgnoredPorts; |
| 55 | // Always allow binds for 53. This is a workaround to unblock Docker Desktop and needs to be revisited in the future. |
| 56 | m_ignoredPorts.insert(c_dnsPortNumber); |
| 57 | |
| 58 | m_hostTcpEphemeralPortRange = QueryHostEphemeralPortRange(L"MSFT_NetTCPSetting"); |
| 59 | m_hostUdpEphemeralPortRange = QueryHostEphemeralPortRange(L"MSFT_NetUDPSetting"); |
| 60 | |
| 61 | WI_ASSERT(m_hostTcpEphemeralPortRange.first <= m_hostTcpEphemeralPortRange.second); |
| 62 | WI_ASSERT(m_hostUdpEphemeralPortRange.first <= m_hostUdpEphemeralPortRange.second); |
| 63 | |
| 64 | WSL_LOG( |
| 65 | "GuestNetworkService::CreateGuestNetworkService - host ephemeral port ranges", |
| 66 | TraceLoggingValue(m_hostTcpEphemeralPortRange.first, "tcpStartPort"), |
| 67 | TraceLoggingValue(m_hostTcpEphemeralPortRange.second, "tcpEndPort"), |
| 68 | TraceLoggingValue(m_hostUdpEphemeralPortRange.first, "udpStartPort"), |
| 69 | TraceLoggingValue(m_hostUdpEphemeralPortRange.second, "udpEndPort")); |
| 70 | |
| 71 | hns::GuestNetworkService request{}; |
| 72 | request.VirtualMachineId = VmId; |
| 73 | request.MirrorHostNetworking = true; |
| 74 | request.SchemaVersion = {2, 0}; |
| 75 | |
| 76 | request.GnsRpcServerInformation.EndpointType = hns::RpcEndpointType::LRpc; |
| 77 | request.GnsRpcServerInformation.ObjectUuid = ServerUuid; |
| 78 | WI_SetFlag(request.Flags, hns::GuestNetworkServiceFlags::IsFlowsteered); |
| 79 | WI_SetFlag(request.Flags, hns::GuestNetworkServiceFlags::IsFlowsteeredSelfManaged); |
| 80 | |
| 81 | wil::unique_cotaskmem_string error; |
| 82 | const auto result = ::HcnCreateGuestNetworkService(VmId, ToJsonW(request).c_str(), &m_service, &error); |
| 83 | WSL_LOG( |
| 84 | "GuestNetworkService::CreateGuestNetworkService [HcnCreateGuestNetworkService]", |
| 85 | TraceLoggingValue(request.VirtualMachineId, "virtualMachineId"), |
| 86 | TraceLoggingValue(request.MirrorHostNetworking, "mirrorHostNetworking"), |
| 87 | TraceLoggingValue(request.SchemaVersion.Major, "schemaMajorVersion"), |
| 88 | TraceLoggingValue(request.SchemaVersion.Minor, "schemaMinorVersion"), |
| 89 | TraceLoggingValue(JsonEnumToString(request.GnsRpcServerInformation.EndpointType).c_str(), "endpointType"), |
| 90 | TraceLoggingValue(request.GnsRpcServerInformation.ObjectUuid, "objectUuid"), |
| 91 | TraceLoggingValue(static_cast<uint32_t>(request.Flags), "flags-value"), |
| 92 | TraceLoggingHResult(result, "result"), |
| 93 | TraceLoggingValue(error.is_valid() ? error.get() : L"null", "errorString")); |
| 94 | THROW_IF_FAILED_MSG(result, "%ls", error.get()); |
| 95 | m_id = VmId; |
| 96 | |
| 97 | m_guestNetworkServiceCallback = windows::common::hcs::RegisterGuestNetworkServiceCallback(m_service, Callback, CallbackContext); |
| 98 | SetGuestNetworkServiceState(hns::GuestNetworkServiceState::Bootstrapping); |
| 99 | } |
| 100 | |
| 101 | void wsl::core::networking::GuestNetworkService::SetGuestNetworkServiceState(_In_ hns::GuestNetworkServiceState State) const |
| 102 | { |
| 103 | hns::ModifyGuestNetworkServiceSettingRequest modifyRequest{}; |
| 104 | modifyRequest.RequestType = hns::ModifyRequestType::Update; |
| 105 | modifyRequest.ResourceType = hns::GuestNetworkServiceResourceType::State; |
| 106 | modifyRequest.Settings.State = State; |
| 107 | |
| 108 | const auto result = ::HcnModifyGuestNetworkService(m_service.get(), ToJsonW(modifyRequest).c_str(), nullptr); |
| 109 | WSL_LOG( |
| 110 | "GuestNetworkService::SetGuestNetworkServiceState [HcnModifyGuestNetworkService]", |
| 111 | TraceLoggingValue(JsonEnumToString(modifyRequest.Settings.State).c_str(), "state")); |
| 112 | THROW_IF_FAILED(result); |
| 113 | } |
| 114 | |
| 115 | std::pair<uint16_t, uint16_t> wsl::core::networking::GuestNetworkService::AllocateEphemeralPortRange() |
| 116 | { |
| 117 | FAIL_FAST_IF(!IsFlowSteeringSupportedByHns()); |
| 118 | |
| 119 | const auto lock = m_dataLock.lock_exclusive(); |
| 120 | |
| 121 | HANDLE port{nullptr}; |
| 122 | auto releasePortOnError = wil::scope_exit([&] { |
| 123 | if (port) |
| 124 | { |
| 125 | m_releasePort.value()(port); |
| 126 | } |
| 127 | }); |
| 128 | |
| 129 | // N.B. Use an odd number of ports to avoid Linux kernel warning about preferring different parity for start / end values. |
| 130 | static constexpr auto c_ephemeralPortRangeSize = 4095; |
| 131 | THROW_IF_FAILED(m_allocatePortRange.value()(m_service.get(), c_ephemeralPortRangeSize, &m_reservedPortRange, &port)); |
| 132 | |
| 133 | WI_ASSERT(m_reservedPortRange.endingPort - m_reservedPortRange.startingPort == c_ephemeralPortRangeSize); |
| 134 | |
| 135 | // Count the overlap of the guest's reserved ephemeral range with the host ephemeral range |
| 136 | // and seed the in-use counters accordingly. |
| 137 | m_hostTcpEphemeralPortsInUse = ComputeHostEphemeralOverlap(IPPROTO_TCP); |
| 138 | m_hostUdpEphemeralPortsInUse = ComputeHostEphemeralOverlap(IPPROTO_UDP); |
| 139 | |
| 140 | // setting the port to zero as we do not expect any bind requests to be sent to wslcore for ports in this range |
| 141 | m_reservedPorts.emplace(std::make_pair(HCN_PORT_PROTOCOL_TCP, static_cast<uint16_t>(0)), HcnPortReservation{port, 1}); |
| 142 | |
| 143 | // ownership of the port was transferred successfully |
| 144 | releasePortOnError.release(); |
| 145 | |
| 146 | WSL_LOG( |
| 147 | "GuestNetworkService::AllocateEphemeralPortRange", |
| 148 | TraceLoggingValue(m_reservedPortRange.startingPort, "startingPort"), |
| 149 | TraceLoggingValue(m_reservedPortRange.endingPort, "endingPort")); |
| 150 | |
| 151 | return std::make_pair(m_reservedPortRange.startingPort, m_reservedPortRange.endingPort); |
| 152 | } |
| 153 | |
| 154 | bool wsl::core::networking::GuestNetworkService::IsPortAllocationLoopbackException(const SOCKADDR_INET& Address) noexcept |
| 155 | { |
| 156 | // Out of IPv4 loopback address range 127.0.0.0/8, only 127.0.0.1 is used by host<->guest loopback networking scenarios. |
| 157 | // FSE needs to be aware of binds using address 127.0.0.1, but can ignore binds for other IPv4 loopback addresses. |
| 158 | // |
| 159 | // Loopback traffic from the guest to the other IPv4 loopback addresses will stay in the guest. |
| 160 | // |
| 161 | // This also solves the issue of someone wanting to bind on the host to port 53 (known scenario is ICS) |
| 162 | // at the same time with someone binding to port 53 in the guest - known scenarios are: |
| 163 | // - DNS tunneling server that uses IP 127.0.0.42, port 53 |
| 164 | // - systemd DNS resolver that uses IP 127.0.0.53, port 53 |
| 165 | return (Address.si_family == AF_INET && IN4_IS_ADDR_LOOPBACK(&Address.Ipv4.sin_addr) && !IN4_ADDR_EQUAL(&Address.Ipv4.sin_addr, &c_ipv4LoopbackAddr)); |
| 166 | } |
| 167 | |
| 168 | bool wsl::core::networking::GuestNetworkService::IsPortAllocationMulticast(const SOCKADDR_INET& Address, _In_ int Protocol) noexcept |
| 169 | { |
| 170 | const auto PortNumber = SS_PORT(&Address); |
| 171 | |
| 172 | if ((Address.si_family == AF_INET && IN4_IS_ADDR_MULTICAST(&Address.Ipv4.sin_addr)) || |
| 173 | (Address.si_family == AF_INET6 && IN6_IS_ADDR_MULTICAST(&Address.Ipv6.sin6_addr))) |
| 174 | { |
| 175 | return true; |
| 176 | } |
| 177 | // multicast DNS (mDNS) |
| 178 | else if (Protocol == IPPROTO_UDP && PortNumber == c_mdnsPortNumber) |
| 179 | { |
| 180 | return true; |
| 181 | } |
| 182 | // LLMNR DNS |
| 183 | else if (Protocol == IPPROTO_UDP && PortNumber == c_llmnrPortNumber) |
| 184 | { |
| 185 | return true; |
| 186 | } |
| 187 | |
| 188 | return false; |
| 189 | } |
| 190 | |
| 191 | std::pair<uint16_t, uint16_t> wsl::core::networking::GuestNetworkService::QueryHostEphemeralPortRange(LPCWSTR WmiClassName) noexcept |
| 192 | try |
| 193 | { |
| 194 | const auto com = wil::CoInitializeEx(); |
| 195 | WmiService service(L"ROOT\\StandardCimv2"); |
| 196 | WmiEnumerate enumSetting(service); |
| 197 | |
| 198 | auto query = std::format(L"SELECT DynamicPortRangeStartPort, DynamicPortRangeNumberOfPorts FROM {}", WmiClassName); |
| 199 | for (const auto& instance : enumSetting.query(query.c_str())) |
| 200 | { |
| 201 | unsigned int startPort = 0; |
| 202 | unsigned int numberOfPorts = 0; |
| 203 | |
| 204 | if (instance.get(L"DynamicPortRangeStartPort", &startPort) && |
| 205 | instance.get(L"DynamicPortRangeNumberOfPorts", &numberOfPorts) && startPort > 0 && numberOfPorts > 0) |
| 206 | { |
| 207 | const auto endPort = static_cast<uint64_t>(startPort) + numberOfPorts - 1; |
| 208 | if (startPort > UINT16_MAX || endPort > UINT16_MAX) |
| 209 | { |
| 210 | LOG_HR_MSG(E_FAIL, "Ephemeral port range overflows uint16_t: start=%u, count=%u", startPort, numberOfPorts); |
| 211 | return c_invalidEphemeralPortRange; |
| 212 | } |
| 213 | |
| 214 | return {static_cast<uint16_t>(startPort), static_cast<uint16_t>(endPort)}; |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | LOG_HR_MSG(E_FAIL, "No valid ephemeral port range found in WMI class %ls", WmiClassName); |
| 219 | return c_invalidEphemeralPortRange; |
| 220 | } |
| 221 | catch (...) |
| 222 | { |
| 223 | LOG_CAUGHT_EXCEPTION_MSG("Failed to query host ephemeral port range from WMI class %ls", WmiClassName); |
| 224 | return c_invalidEphemeralPortRange; |
| 225 | } |
| 226 | |
| 227 | bool wsl::core::networking::GuestNetworkService::IsPortInHostEphemeralRange(uint16_t PortNumber, int Protocol) const noexcept |
| 228 | { |
| 229 | const auto& range = (Protocol == IPPROTO_UDP) ? m_hostUdpEphemeralPortRange : m_hostTcpEphemeralPortRange; |
| 230 | return PortNumber >= range.first && PortNumber <= range.second; |
| 231 | } |
| 232 | |
| 233 | bool wsl::core::networking::GuestNetworkService::IsPortInGuestEphemeralRange(uint16_t PortNumber) const noexcept |
| 234 | { |
| 235 | return PortNumber >= m_reservedPortRange.startingPort && PortNumber <= m_reservedPortRange.endingPort; |
| 236 | } |
| 237 | |
| 238 | uint16_t wsl::core::networking::GuestNetworkService::ComputeHostEphemeralPortCap(int Protocol) const noexcept |
| 239 | { |
| 240 | const auto& range = (Protocol == IPPROTO_UDP) ? m_hostUdpEphemeralPortRange : m_hostTcpEphemeralPortRange; |
| 241 | |
| 242 | // Cap the guest at half of the host ephemeral range so it can't exhaust the host's ports. The |
| 243 | // host range is always at least 255, so the cap will fit in uint16_t, no risk of overflow. |
| 244 | return static_cast<uint16_t>((range.second - range.first + 1) / 2); |
| 245 | } |
| 246 | |
| 247 | uint16_t wsl::core::networking::GuestNetworkService::ComputeHostEphemeralOverlap(int Protocol) const noexcept |
| 248 | { |
| 249 | const auto& range = (Protocol == IPPROTO_UDP) ? m_hostUdpEphemeralPortRange : m_hostTcpEphemeralPortRange; |
| 250 | |
| 251 | // Number of guest reserved ports that fall within the host ephemeral range. |
| 252 | const uint16_t overlapStart = std::max<uint16_t>(m_reservedPortRange.startingPort, range.first); |
| 253 | const uint16_t overlapEnd = std::min<uint16_t>(m_reservedPortRange.endingPort, range.second); |
| 254 | return (overlapStart <= overlapEnd) ? static_cast<uint16_t>(overlapEnd - overlapStart + 1) : 0; |
| 255 | } |
| 256 | |
| 257 | int wsl::core::networking::GuestNetworkService::OnPortAllocationRequest(const SOCKADDR_INET& Address, _In_ int Protocol, _In_ bool Allocate) noexcept |
| 258 | try |
| 259 | { |
| 260 | // The Linux and Windows constants conveniently have the same values for TCP & UDP. |
| 261 | WI_ASSERT(Protocol == IPPROTO_TCP || Protocol == IPPROTO_UDP); |
| 262 | WI_ASSERT(m_allocatePort.has_value() && m_releasePort.has_value()); |
| 263 | auto HnsProtocol = Protocol == IPPROTO_TCP ? HCN_PORT_PROTOCOL_TCP : HCN_PORT_PROTOCOL_UDP; |
| 264 | |
| 265 | const auto PortNumber = SS_PORT(&Address); |
| 266 | const auto StringAddress = wsl::windows::common::string::SockAddrInetToString(Address); |
| 267 | |
| 268 | if (IsPortAllocationLoopbackException(Address)) |
| 269 | { |
| 270 | WSL_LOG( |
| 271 | "GuestNetworkService::OnPortAllocationRequest - allowing port allocation for loopback without asking FSE", |
| 272 | TraceLoggingValue(StringAddress.c_str(), "IP address"), |
| 273 | TraceLoggingValue(Protocol == IPPROTO_TCP ? "TCP" : "UDP", "protocol"), |
| 274 | TraceLoggingValue(PortNumber, "portNumber"), |
| 275 | TraceLoggingValue(Address.si_family == AF_INET ? "IPv4" : "IPv6", "address family"), |
| 276 | TraceLoggingValue(Allocate, "Allocate")); |
| 277 | return 0; |
| 278 | } |
| 279 | |
| 280 | if (m_ignoredPorts.find(PortNumber) != m_ignoredPorts.end()) |
| 281 | { |
| 282 | |
| 283 | WSL_LOG( |
| 284 | "GuestNetworkService::OnPortAllocationRequest - allowing port allocation for ignored port without asking FSE", |
| 285 | TraceLoggingValue(StringAddress.c_str(), "IP address"), |
| 286 | TraceLoggingValue(Protocol == IPPROTO_TCP ? "TCP" : "UDP", "protocol"), |
| 287 | TraceLoggingValue(PortNumber, "portNumber"), |
| 288 | TraceLoggingValue(Address.si_family == AF_INET ? "IPv4" : "IPv6", "address family"), |
| 289 | TraceLoggingValue(Allocate, "Allocate")); |
| 290 | return 0; |
| 291 | } |
| 292 | |
| 293 | const auto lock = m_dataLock.lock_exclusive(); |
| 294 | |
| 295 | if (IsPortInGuestEphemeralRange(PortNumber)) |
| 296 | { |
| 297 | WSL_LOG( |
| 298 | "GuestNetworkService::OnPortAllocationRequest", |
| 299 | TraceLoggingValue( |
| 300 | "Guest attempted to allocate a port but it was already allocated through port reservations", "status"), |
| 301 | TraceLoggingValue(HnsProtocol == HCN_PORT_PROTOCOL_TCP ? "TCP" : "UDP", "protocol"), |
| 302 | TraceLoggingValue(PortNumber, "portNumber"), |
| 303 | TraceLoggingValue(StringAddress.c_str(), "IP address")); |
| 304 | return 0; |
| 305 | } |
| 306 | |
| 307 | const bool isHostEphemeralPort = IsPortInHostEphemeralRange(PortNumber, Protocol); |
| 308 | |
| 309 | HRESULT result = E_UNEXPECTED; |
| 310 | const auto it = m_reservedPorts.find(std::make_pair(HnsProtocol, PortNumber)); |
| 311 | if (Allocate) |
| 312 | { |
| 313 | if (it != m_reservedPorts.end()) |
| 314 | { |
| 315 | it->second.ReferenceCount++; |
| 316 | WSL_LOG( |
| 317 | "GuestNetworkService::OnPortAllocationRequest - incremented reference", |
| 318 | TraceLoggingValue(PortNumber, "Port"), |
| 319 | TraceLoggingValue(Address.si_family, "Family"), |
| 320 | TraceLoggingValue(StringAddress.c_str(), "IP address"), |
| 321 | TraceLoggingValue(Protocol, "Protocol"), |
| 322 | TraceLoggingValue(it->second.ReferenceCount, "ReferenceCount")); |
| 323 | return 0; |
| 324 | } |
| 325 | |
| 326 | // New reservation for a port in the host ephemeral range: enforce the cap. |
| 327 | if (isHostEphemeralPort) |
| 328 | { |
| 329 | const auto cap = ComputeHostEphemeralPortCap(Protocol); |
| 330 | const auto portsInUse = (Protocol == IPPROTO_UDP) ? m_hostUdpEphemeralPortsInUse : m_hostTcpEphemeralPortsInUse; |
| 331 | if (portsInUse >= cap) |
| 332 | { |
| 333 | WSL_LOG( |
| 334 | "GuestNetworkService::OnPortAllocationRequest - denying port in host ephemeral range, cap reached", |
| 335 | TraceLoggingValue(StringAddress.c_str(), "IP address"), |
| 336 | TraceLoggingValue(Protocol == IPPROTO_TCP ? "TCP" : "UDP", "protocol"), |
| 337 | TraceLoggingValue(PortNumber, "portNumber"), |
| 338 | TraceLoggingValue(portsInUse, "hostEphemeralPortsInUse"), |
| 339 | TraceLoggingValue(cap, "hostEphemeralPortCap")); |
| 340 | return -LX_EADDRINUSE; |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | HANDLE port{nullptr}; |
| 345 | auto releasePortOnError = wil::scope_exit([&] { |
| 346 | if (port) |
| 347 | { |
| 348 | m_releasePort.value()(port); |
| 349 | } |
| 350 | }); |
| 351 | |
| 352 | bool isMulticast = IsPortAllocationMulticast(Address, Protocol); |
| 353 | |
| 354 | // Multicast port allocations are requested using the "shared" flag. |
| 355 | result = m_allocatePort.value()( |
| 356 | m_service.get(), HnsProtocol, isMulticast ? HCN_PORT_ACCESS_SHARED : HCN_PORT_ACCESS_EXCLUSIVE, PortNumber, &port); |
| 357 | |
| 358 | if (SUCCEEDED(result)) |
| 359 | { |
| 360 | m_reservedPorts.emplace(std::make_pair(HnsProtocol, PortNumber), HcnPortReservation{port, 1}); |
| 361 | |
| 362 | if (isHostEphemeralPort) |
| 363 | { |
| 364 | auto& portsInUse = Protocol == IPPROTO_UDP ? m_hostUdpEphemeralPortsInUse : m_hostTcpEphemeralPortsInUse; |
| 365 | portsInUse++; |
| 366 | } |
| 367 | } |
| 368 | // if the port was reserved, we successfully handed over ownership |
| 369 | releasePortOnError.release(); |
| 370 | |
| 371 | WSL_LOG( |
| 372 | "GuestNetworkService::OnPortAllocationRequest [HcnReserveGuestNetworkServicePort]", |
| 373 | TraceLoggingValue(HnsProtocol == HCN_PORT_PROTOCOL_TCP ? "TCP" : "UDP", "protocol"), |
| 374 | TraceLoggingValue(PortNumber, "portNumber"), |
| 375 | TraceLoggingValue(StringAddress.c_str(), "IP address"), |
| 376 | TraceLoggingValue(isMulticast, "isMulticast"), |
| 377 | TraceLoggingValue(result, "result")); |
| 378 | } |
| 379 | else |
| 380 | { |
| 381 | if (it == m_reservedPorts.end()) |
| 382 | { |
| 383 | RETURN_HR_MSG(E_UNEXPECTED, "Guest attempted to deallocate port (%i, %i), but it's not allocated", Protocol, PortNumber); |
| 384 | } |
| 385 | |
| 386 | if (it->second.ReferenceCount == 1) |
| 387 | { |
| 388 | result = m_releasePort.value()(it->second.Handle); |
| 389 | m_reservedPorts.erase(it); |
| 390 | |
| 391 | // Only decrement the in-use counter when the release actually succeeded. If the release |
| 392 | // failed the reservation may still exist on the host, and undercounting would let the |
| 393 | // guest exceed the intended cap. |
| 394 | if (isHostEphemeralPort && SUCCEEDED(result)) |
| 395 | { |
| 396 | auto& portsInUse = (Protocol == IPPROTO_UDP) ? m_hostUdpEphemeralPortsInUse : m_hostTcpEphemeralPortsInUse; |
| 397 | WI_ASSERT(portsInUse > 0); |
| 398 | portsInUse--; |
| 399 | } |
| 400 | |
| 401 | WSL_LOG( |
| 402 | "GuestNetworkService::OnPortAllocationRequest - released port", |
| 403 | TraceLoggingValue(PortNumber, "Port"), |
| 404 | TraceLoggingValue(Address.si_family, "Family"), |
| 405 | TraceLoggingValue(StringAddress.c_str(), "IP address"), |
| 406 | TraceLoggingValue(Protocol, "Protocol")); |
| 407 | } |
| 408 | else |
| 409 | { |
| 410 | it->second.ReferenceCount--; |
| 411 | WSL_LOG( |
| 412 | "GuestNetworkService::OnPortAllocationRequest - decremented reference", |
| 413 | TraceLoggingValue(PortNumber, "Port"), |
| 414 | TraceLoggingValue(Address.si_family, "Family"), |
| 415 | TraceLoggingValue(StringAddress.c_str(), "IP address"), |
| 416 | TraceLoggingValue(Protocol, "Protocol"), |
| 417 | TraceLoggingValue(it->second.ReferenceCount, "ReferenceCount")); |
| 418 | return 0; |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | return SUCCEEDED(result) ? 0 : -LX_EADDRINUSE; |
| 423 | } |
| 424 | catch (...) |
| 425 | { |
| 426 | LOG_CAUGHT_EXCEPTION(); |
| 427 | return -LX_ENOBUFS; |
| 428 | } |
| 429 | |
| 430 | void wsl::core::networking::GuestNetworkService::Stop() noexcept |
| 431 | { |
| 432 | if (m_releasePort) |
| 433 | { |
| 434 | const auto lock = m_dataLock.lock_exclusive(); |
| 435 | |
| 436 | for (const auto& reservedPort : m_reservedPorts) |
| 437 | { |
| 438 | m_releasePort.value()(reservedPort.second.Handle); |
| 439 | } |
| 440 | m_reservedPorts.clear(); |
| 441 | |
| 442 | m_hostTcpEphemeralPortsInUse = 0; |
| 443 | m_hostUdpEphemeralPortsInUse = 0; |
| 444 | } |
| 445 | |
| 446 | m_guestNetworkServiceCallback.reset(); |
| 447 | |
| 448 | if (m_service) |
| 449 | { |
| 450 | wil::unique_cotaskmem_string error; |
| 451 | const auto result = ::HcnDeleteGuestNetworkService(m_id, &error); |
| 452 | LOG_IF_FAILED_MSG(result, "HcnDeleteGuestNetworkService failed, %ls", error.get()); |
| 453 | m_service.reset(); |
| 454 | } |
| 455 | } |