| 1 | // Copyright (C) Microsoft Corporation. All rights reserved. |
| 2 | |
| 3 | #include "precomp.h" |
| 4 | #include "ConsommeNetworking.h" |
| 5 | #include "GuestDeviceManager.h" |
| 6 | #include "Stringify.h" |
| 7 | #include "stringshared.h" |
| 8 | |
| 9 | using namespace wsl::core::networking; |
| 10 | using namespace wsl::shared; |
| 11 | using namespace wsl::windows::common::stringify; |
| 12 | using wsl::core::ConsommeNetworking; |
| 13 | |
| 14 | static constexpr auto c_eth0DeviceName = L"eth0"; |
| 15 | static constexpr auto c_loopbackDeviceName = TEXT(LX_INIT_LOOPBACK_DEVICE_NAME); |
| 16 | static constexpr wsl::shared::string::MacAddress c_defaultClientMacAddress{0x00, 0x00, 0x00, 0x00, 0x01, 0x00}; |
| 17 | static constexpr wsl::shared::string::MacAddress c_gatewayMacAddress{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; |
| 18 | |
| 19 | namespace { |
| 20 | |
| 21 | EthernetAddress ToEthernetAddress(const wsl::shared::string::MacAddress& address) |
| 22 | { |
| 23 | EthernetAddress result{}; |
| 24 | std::copy(address.begin(), address.end(), std::begin(result.bytes)); |
| 25 | return result; |
| 26 | } |
| 27 | |
| 28 | Ipv4Address ToIpv4Address(const SOCKADDR_INET& address) |
| 29 | { |
| 30 | Ipv4Address result{}; |
| 31 | if (address.si_family == AF_INET) |
| 32 | { |
| 33 | result.value = address.Ipv4.sin_addr.S_un.S_addr; |
| 34 | } |
| 35 | |
| 36 | return result; |
| 37 | } |
| 38 | |
| 39 | Ipv6Address ToIpv6Address(const SOCKADDR_INET& address) |
| 40 | { |
| 41 | Ipv6Address result{}; |
| 42 | if (address.si_family == AF_INET6) |
| 43 | { |
| 44 | std::copy(std::begin(address.Ipv6.sin6_addr.u.Byte), std::end(address.Ipv6.sin6_addr.u.Byte), std::begin(result.bytes)); |
| 45 | } |
| 46 | |
| 47 | return result; |
| 48 | } |
| 49 | |
| 50 | IpAddress ToIpAddress(const SOCKADDR_INET& address) |
| 51 | { |
| 52 | IpAddress result{}; |
| 53 | if (address.si_family == AF_INET) |
| 54 | { |
| 55 | result.family = IpAddressFamily_V4; |
| 56 | std::copy( |
| 57 | reinterpret_cast<const BYTE*>(&address.Ipv4.sin_addr), |
| 58 | reinterpret_cast<const BYTE*>(&address.Ipv4.sin_addr) + sizeof(address.Ipv4.sin_addr), |
| 59 | std::begin(result.bytes)); |
| 60 | } |
| 61 | else if (address.si_family == AF_INET6) |
| 62 | { |
| 63 | result.family = IpAddressFamily_V6; |
| 64 | std::copy(std::begin(address.Ipv6.sin6_addr.u.Byte), std::end(address.Ipv6.sin6_addr.u.Byte), std::begin(result.bytes)); |
| 65 | } |
| 66 | |
| 67 | return result; |
| 68 | } |
| 69 | |
| 70 | bool AreEqual(const IpAddress& left, const IpAddress& right) |
| 71 | { |
| 72 | return left.family == right.family && std::equal(std::begin(left.bytes), std::end(left.bytes), std::begin(right.bytes)); |
| 73 | } |
| 74 | |
| 75 | bool AreEqual(const std::vector<IpAddress>& left, const std::vector<IpAddress>& right) |
| 76 | { |
| 77 | return std::ranges::equal( |
| 78 | left, right, [](const auto& leftAddress, const auto& rightAddress) { return AreEqual(leftAddress, rightAddress); }); |
| 79 | } |
| 80 | |
| 81 | bool AreEqual(const WslVirtioNetConfig& left, const WslVirtioNetConfig& right) |
| 82 | { |
| 83 | return left.clientIp.value == right.clientIp.value && left.hasClientIpv6 == right.hasClientIpv6 && |
| 84 | std::equal(std::begin(left.clientIpv6.bytes), std::end(left.clientIpv6.bytes), std::begin(right.clientIpv6.bytes)) && |
| 85 | std::equal(std::begin(left.clientMac.bytes), std::end(left.clientMac.bytes), std::begin(right.clientMac.bytes)) && |
| 86 | left.gatewayIp.value == right.gatewayIp.value && |
| 87 | std::equal(std::begin(left.gatewayMac.bytes), std::end(left.gatewayMac.bytes), std::begin(right.gatewayMac.bytes)) && |
| 88 | std::equal(std::begin(left.gatewayMacIpv6.bytes), std::end(left.gatewayMacIpv6.bytes), std::begin(right.gatewayMacIpv6.bytes)) && |
| 89 | left.netmask.value == right.netmask.value; |
| 90 | } |
| 91 | |
| 92 | std::vector<IpAddress> ToIpAddresses(const DnsInfo& dns) |
| 93 | { |
| 94 | std::vector<IpAddress> result; |
| 95 | result.reserve(dns.Servers.size()); |
| 96 | for (const auto& server : dns.Servers) |
| 97 | { |
| 98 | if (server.empty()) |
| 99 | { |
| 100 | continue; |
| 101 | } |
| 102 | |
| 103 | result.emplace_back(ToIpAddress(wsl::windows::common::string::StringToSockAddrInet(wsl::shared::string::MultiByteToWide(server)))); |
| 104 | } |
| 105 | |
| 106 | return result; |
| 107 | } |
| 108 | |
| 109 | WslVirtioNetConfig BuildVirtioNetConfig( |
| 110 | const std::shared_ptr<NetworkSettings>& networkSettings, bool enableIpv6, std::optional<wsl::shared::string::MacAddress> clientMacAddress = {}) |
| 111 | { |
| 112 | ULONG netmask{}; |
| 113 | if (networkSettings->PreferredIpAddress.Address.si_family == AF_INET) |
| 114 | { |
| 115 | LOG_IF_WIN32_ERROR(ConvertLengthToIpv4Mask(networkSettings->PreferredIpAddress.PrefixLength, &netmask)); |
| 116 | } |
| 117 | |
| 118 | WslVirtioNetConfig config{}; |
| 119 | config.clientIp = ToIpv4Address(networkSettings->PreferredIpAddress.Address); |
| 120 | config.hasClientIpv6 = enableIpv6 && networkSettings->PreferredIpv6Address.Address.si_family == AF_INET6; |
| 121 | config.clientIpv6 = ToIpv6Address(networkSettings->PreferredIpv6Address.Address); |
| 122 | config.clientMac = ToEthernetAddress(clientMacAddress.value_or(c_defaultClientMacAddress)); |
| 123 | config.gatewayIp = ToIpv4Address(networkSettings->GetBestGatewayAddress()); |
| 124 | config.gatewayMac = ToEthernetAddress(c_gatewayMacAddress); |
| 125 | config.gatewayMacIpv6 = ToEthernetAddress(c_gatewayMacAddress); |
| 126 | config.netmask.value = netmask; |
| 127 | return config; |
| 128 | } |
| 129 | |
| 130 | } // namespace |
| 131 | |
| 132 | ConsommeNetworking::ConsommeNetworking( |
| 133 | GnsChannel&& gnsChannel, |
| 134 | ConsommeNetworkingFlags flags, |
| 135 | LPCWSTR dnsOptions, |
| 136 | LPCSTR hostLoopback, |
| 137 | std::shared_ptr<GuestDeviceManager> guestDeviceManager, |
| 138 | wil::shared_handle userToken) : |
| 139 | m_guestDeviceManager(std::move(guestDeviceManager)), |
| 140 | m_userToken(std::move(userToken)), |
| 141 | m_gnsChannel(std::move(gnsChannel)), |
| 142 | m_flags(flags), |
| 143 | m_dnsOptions(dnsOptions), |
| 144 | m_hostLoopback(hostLoopback ? hostLoopback : "") |
| 145 | { |
| 146 | } |
| 147 | |
| 148 | ConsommeNetworking::~ConsommeNetworking() |
| 149 | { |
| 150 | // Unregister the network notification callback to prevent it from using the GNS channel. |
| 151 | m_networkNotifyHandle.reset(); |
| 152 | |
| 153 | // Stop the GNS channel to unblock any stuck communications with the guest. |
| 154 | m_gnsChannel.Stop(); |
| 155 | } |
| 156 | |
| 157 | void ConsommeNetworking::Initialize() |
| 158 | { |
| 159 | // Initialize adapter state. |
| 160 | RefreshGuestConnection(); |
| 161 | |
| 162 | if (WI_IsFlagSet(m_flags, ConsommeNetworkingFlags::LocalhostRelay)) |
| 163 | { |
| 164 | SetupLoopbackDevice(); |
| 165 | } |
| 166 | |
| 167 | if (!m_hostLoopback.empty()) |
| 168 | { |
| 169 | THROW_HR_IF(E_UNEXPECTED, !WI_IsFlagSet(m_flags, ConsommeNetworkingFlags::LocalhostRelay)); |
| 170 | SetupHostLoopback(); |
| 171 | } |
| 172 | |
| 173 | THROW_IF_WIN32_ERROR(NotifyNetworkConnectivityHintChange(&ConsommeNetworking::OnNetworkConnectivityChange, this, TRUE, &m_networkNotifyHandle)); |
| 174 | } |
| 175 | |
| 176 | void ConsommeNetworking::TraceLoggingRundown() noexcept |
| 177 | { |
| 178 | auto lock = m_lock.lock_exclusive(); |
| 179 | |
| 180 | WSL_LOG("ConsommeNetworking::TraceLoggingRundown", TRACE_NETWORKSETTINGS_OBJECT(m_networkSettings)); |
| 181 | } |
| 182 | |
| 183 | void ConsommeNetworking::FillInitialConfiguration(LX_MINI_INIT_NETWORKING_CONFIGURATION& message) |
| 184 | { |
| 185 | message.NetworkingMode = LxMiniInitNetworkingModeConsomme; |
| 186 | message.DisableIpv6 = WI_IsFlagClear(m_flags, ConsommeNetworkingFlags::Ipv6); |
| 187 | message.EnableDhcpClient = false; |
| 188 | message.PortTrackerType = LX_MINI_INIT_PORT_TRACKER_TYPE::LxMiniInitPortTrackerTypeMirrored; |
| 189 | } |
| 190 | |
| 191 | void ConsommeNetworking::StartPortTracker(wil::unique_socket&& socket) |
| 192 | { |
| 193 | WI_ASSERT(!m_gnsPortTrackerChannel.has_value()); |
| 194 | |
| 195 | m_gnsPortTrackerChannel.emplace( |
| 196 | std::move(socket), |
| 197 | [&](const SOCKADDR_INET& addr, int protocol, bool allocate) { |
| 198 | return wil::ResultFromException([&]() { |
| 199 | HandlePortNotification(addr, protocol, INETADDR_PORT(reinterpret_cast<const SOCKADDR*>(&addr)), allocate); |
| 200 | }); |
| 201 | }, |
| 202 | [](const std::string&, bool) {}); // TODO: reconsider if InterfaceStateCallback is needed. |
| 203 | } |
| 204 | |
| 205 | void NETIOAPI_API_ ConsommeNetworking::OnNetworkConnectivityChange(PVOID context, NL_NETWORK_CONNECTIVITY_HINT hint) |
| 206 | try |
| 207 | { |
| 208 | static_cast<ConsommeNetworking*>(context)->RefreshGuestConnection(); |
| 209 | } |
| 210 | CATCH_LOG() |
| 211 | |
| 212 | uint16_t ConsommeNetworking::HandlePortNotification(const SOCKADDR_INET& addr, int protocol, uint16_t guestPort, bool allocate) const |
| 213 | { |
| 214 | if (addr.si_family == AF_INET6 && WI_IsFlagClear(m_flags, ConsommeNetworkingFlags::Ipv6)) |
| 215 | { |
| 216 | return 0; |
| 217 | } |
| 218 | |
| 219 | const auto ipAddress = (addr.si_family == AF_INET) ? reinterpret_cast<const void*>(&addr.Ipv4.sin_addr) |
| 220 | : reinterpret_cast<const void*>(&addr.Ipv6.sin6_addr); |
| 221 | const bool loopback = INET_IS_ADDR_LOOPBACK(addr.si_family, ipAddress); |
| 222 | const bool unspecified = INET_IS_ADDR_UNSPECIFIED(addr.si_family, ipAddress); |
| 223 | if (addr.si_family == AF_INET && loopback) |
| 224 | { |
| 225 | // Only intercepting 127.0.0.1; any other loopback address will remain on 'lo'. |
| 226 | if (addr.Ipv4.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) |
| 227 | { |
| 228 | return 0; |
| 229 | } |
| 230 | } |
| 231 | SOCKADDR_INET localAddr = addr; |
| 232 | std::function<void()> removePort; |
| 233 | |
| 234 | auto hostPort = INETADDR_PORT(reinterpret_cast<const SOCKADDR*>(&addr)); |
| 235 | auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&removePort]() { |
| 236 | if (removePort) |
| 237 | { |
| 238 | removePort(); |
| 239 | } |
| 240 | }); |
| 241 | |
| 242 | if (WI_IsFlagSet(m_flags, ConsommeNetworkingFlags::LocalhostRelay) && (unspecified || loopback)) |
| 243 | { |
| 244 | if (!loopback) |
| 245 | { |
| 246 | INETADDR_SETLOOPBACK(reinterpret_cast<PSOCKADDR>(&localAddr)); |
| 247 | if (addr.si_family == AF_INET) |
| 248 | { |
| 249 | localAddr.Ipv4.sin_port = addr.Ipv4.sin_port; |
| 250 | } |
| 251 | else |
| 252 | { |
| 253 | localAddr.Ipv6.sin6_port = addr.Ipv6.sin6_port; |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | hostPort = ModifyOpenPorts(c_loopbackDeviceName, localAddr, hostPort, guestPort, protocol, allocate); |
| 258 | |
| 259 | // Revert the change on failure. |
| 260 | removePort = [&]() { ModifyOpenPorts(c_loopbackDeviceName, localAddr, hostPort, guestPort, protocol, !allocate); }; |
| 261 | } |
| 262 | |
| 263 | if (!loopback) |
| 264 | { |
| 265 | hostPort = ModifyOpenPorts(c_eth0DeviceName, addr, hostPort, guestPort, protocol, allocate); |
| 266 | } |
| 267 | |
| 268 | cleanup.release(); |
| 269 | |
| 270 | return hostPort; |
| 271 | } |
| 272 | |
| 273 | uint16_t ConsommeNetworking::ModifyOpenPorts( |
| 274 | _In_ PCWSTR tag, _In_ const SOCKADDR_INET& hostAddress, _In_ uint16_t HostPort, _In_ uint16_t GuestPort, _In_ int protocol, _In_ bool isOpen) const |
| 275 | { |
| 276 | THROW_HR_IF_MSG( |
| 277 | HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), |
| 278 | protocol != IPPROTO_TCP && protocol != IPPROTO_UDP, |
| 279 | "Unsupported bind protocol %d", |
| 280 | protocol); |
| 281 | |
| 282 | auto lock = m_lock.lock_exclusive(); |
| 283 | const auto device = m_guestDeviceManager->GetVirtioNetDevice(tag); |
| 284 | const auto transportProtocol = (protocol == IPPROTO_UDP) ? TransportProtocol_Udp : TransportProtocol_Tcp; |
| 285 | auto listenAddress = ToIpAddress(hostAddress); |
| 286 | |
| 287 | if (isOpen) |
| 288 | { |
| 289 | UINT16 allocatedPort{}; |
| 290 | THROW_IF_FAILED(device->BindPort(transportProtocol, &listenAddress, HostPort, GuestPort, &allocatedPort)); |
| 291 | WSL_LOG( |
| 292 | "MapVirtioPort", |
| 293 | TraceLoggingValue(tag, "Tag"), |
| 294 | TraceLoggingValue(HostPort, "HostPort"), |
| 295 | TraceLoggingValue(GuestPort, "GuestPort")); |
| 296 | return allocatedPort; |
| 297 | } |
| 298 | |
| 299 | THROW_IF_FAILED(device->UnbindPort(transportProtocol, listenAddress.family, GuestPort)); |
| 300 | WSL_LOG( |
| 301 | "UnmapVirtioPort", |
| 302 | TraceLoggingValue(tag, "Tag"), |
| 303 | TraceLoggingValue(HostPort, "HostPort"), |
| 304 | TraceLoggingValue(GuestPort, "GuestPort")); |
| 305 | return HostPort; |
| 306 | } |
| 307 | |
| 308 | HRESULT ConsommeNetworking::MapPort(_In_ const SOCKADDR_INET& ListenAddress, _In_ USHORT GuestPort, _In_ int Protocol, _Out_ USHORT* AllocatedHostPort) const |
| 309 | try |
| 310 | { |
| 311 | RETURN_HR_IF(E_POINTER, AllocatedHostPort == nullptr); |
| 312 | RETURN_HR_IF_MSG(E_INVALIDARG, Protocol != IPPROTO_TCP && Protocol != IPPROTO_UDP, "Invalid protocol: %i", Protocol); |
| 313 | |
| 314 | *AllocatedHostPort = 0; |
| 315 | |
| 316 | *AllocatedHostPort = HandlePortNotification(ListenAddress, Protocol, GuestPort, true); |
| 317 | |
| 318 | return S_OK; |
| 319 | } |
| 320 | CATCH_RETURN() |
| 321 | |
| 322 | HRESULT ConsommeNetworking::UnmapPort(_In_ const SOCKADDR_INET& ListenAddress, _In_ USHORT GuestPort, _In_ int Protocol) const |
| 323 | try |
| 324 | { |
| 325 | RETURN_HR_IF(E_INVALIDARG, Protocol != IPPROTO_TCP && Protocol != IPPROTO_UDP); |
| 326 | |
| 327 | HandlePortNotification(ListenAddress, Protocol, GuestPort, false); |
| 328 | |
| 329 | return S_OK; |
| 330 | } |
| 331 | CATCH_RETURN() |
| 332 | |
| 333 | void ConsommeNetworking::RefreshGuestConnection() |
| 334 | { |
| 335 | // Query current networking information before acquiring the lock. |
| 336 | auto networkSettings = GetHostEndpointSettings(); |
| 337 | |
| 338 | std::wstring default_route = networkSettings->GetBestGatewayAddressString(); |
| 339 | |
| 340 | networking::DnsInfo currentDns{}; |
| 341 | if (WI_IsFlagSet(m_flags, ConsommeNetworkingFlags::DnsTunneling)) |
| 342 | { |
| 343 | currentDns = networking::HostDnsInfo::GetDnsTunnelingSettings(default_route); |
| 344 | } |
| 345 | else |
| 346 | { |
| 347 | wsl::core::networking::DnsSettingsFlags dnsFlags = networking::DnsSettingsFlags::IncludeVpn; |
| 348 | WI_SetFlagIf(dnsFlags, networking::DnsSettingsFlags::IncludeIpv6Servers, WI_IsFlagSet(m_flags, ConsommeNetworkingFlags::Ipv6)); |
| 349 | currentDns = networking::HostDnsInfo::GetDnsSettings(dnsFlags); |
| 350 | } |
| 351 | |
| 352 | const auto minMtu = GetMinimumConnectedInterfaceMtu(); |
| 353 | auto virtioNetConfig = BuildVirtioNetConfig(networkSettings, WI_IsFlagSet(m_flags, ConsommeNetworkingFlags::Ipv6)); |
| 354 | auto nameservers = ToIpAddresses(currentDns); |
| 355 | |
| 356 | // Acquire the lock and perform device updates. |
| 357 | auto lock = m_lock.lock_exclusive(); |
| 358 | |
| 359 | // Add the virtio net adapter to the guest, or update its runtime configuration. |
| 360 | if (!m_adapterId.has_value()) |
| 361 | { |
| 362 | WSL_LOG( |
| 363 | "RefreshVirtioNetConnection", |
| 364 | TraceLoggingValue(networkSettings->PreferredIpAddress.AddressString.c_str(), "ClientIp"), |
| 365 | TraceLoggingValue(networkSettings->PreferredIpAddress.PrefixLength, "PrefixLength"), |
| 366 | TraceLoggingValue(default_route.c_str(), "GatewayIp"), |
| 367 | TraceLoggingValue(networkSettings->PreferredIpv6Address.AddressString.c_str(), "ClientIpv6")); |
| 368 | m_adapterId = m_guestDeviceManager->AddVirtioNetDevice(c_eth0DeviceName, virtioNetConfig, nameservers, m_userToken.get()); |
| 369 | } |
| 370 | else if (!m_virtioNetConfig.has_value() || !AreEqual(m_virtioNetConfig.value(), virtioNetConfig) || !AreEqual(m_virtioNetNameservers, nameservers)) |
| 371 | { |
| 372 | IpAddress emptyNameserver{}; |
| 373 | auto* nameserversData = nameservers.empty() ? &emptyNameserver : nameservers.data(); |
| 374 | const auto device = m_guestDeviceManager->GetVirtioNetDevice(c_eth0DeviceName); |
| 375 | THROW_IF_FAILED(device->Update(&virtioNetConfig, gsl::narrow_cast<UINT32>(nameservers.size()), nameserversData)); |
| 376 | } |
| 377 | |
| 378 | m_virtioNetConfig = virtioNetConfig; |
| 379 | m_virtioNetNameservers = std::move(nameservers); |
| 380 | |
| 381 | UpdateIpv4Address(networkSettings->PreferredIpAddress); |
| 382 | if (WI_IsFlagSet(m_flags, ConsommeNetworkingFlags::Ipv6)) |
| 383 | { |
| 384 | UpdateIpv6Address(networkSettings->PreferredIpv6Address); |
| 385 | } |
| 386 | |
| 387 | UpdateDefaultRoute(default_route); |
| 388 | |
| 389 | UpdateDnsSettings(currentDns); |
| 390 | UpdateMtu(minMtu); |
| 391 | |
| 392 | m_networkSettings = std::move(networkSettings); |
| 393 | } |
| 394 | |
| 395 | void ConsommeNetworking::SetupHostLoopback() |
| 396 | { |
| 397 | const auto loopbackDevice = m_guestDeviceManager->GetVirtioNetDevice(c_loopbackDeviceName); |
| 398 | |
| 399 | SOCKADDR_INET loopback{}; |
| 400 | loopback.Ipv4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| 401 | loopback.Ipv4.sin_family = AF_INET; |
| 402 | auto loopbackIp = ToIpAddress(loopback); |
| 403 | |
| 404 | IpAddress virtualAddress; |
| 405 | THROW_IF_FAILED(loopbackDevice->CreateVirtualAddress(&loopbackIp, &virtualAddress)); |
| 406 | |
| 407 | uint32_t addressBytes{}; |
| 408 | std::memcpy(&addressBytes, &virtualAddress.bytes[0], sizeof(addressBytes)); |
| 409 | |
| 410 | std::string virtualAddressString(INET_ADDRSTRLEN, '\0'); |
| 411 | RtlIpv4AddressToStringA(reinterpret_cast<const IN_ADDR*>(&virtualAddress.bytes[0]), virtualAddressString.data()); |
| 412 | virtualAddressString.resize(std::strlen(virtualAddressString.data())); |
| 413 | |
| 414 | const auto eth0 = m_guestDeviceManager->GetVirtioNetDevice(c_eth0DeviceName); |
| 415 | THROW_IF_FAILED(eth0->CreateDNSRecord(DnsRecordType_A, m_hostLoopback.c_str(), virtualAddressString.c_str())); |
| 416 | |
| 417 | WSL_LOG( |
| 418 | "SetupHostLoopback", |
| 419 | TraceLoggingValue(m_hostLoopback.c_str(), "DnsName"), |
| 420 | TraceLoggingValue(virtualAddressString.c_str(), "VirtualAddress")); |
| 421 | } |
| 422 | |
| 423 | void ConsommeNetworking::SetupLoopbackDevice() |
| 424 | { |
| 425 | auto loopbackSettings = std::make_shared<NetworkSettings>(); |
| 426 | const auto* clientIp = WI_IsFlagSet(m_flags, ConsommeNetworkingFlags::LoopbackClientIp) ? L"127.0.0.1" : L"169.254.73.250"; |
| 427 | loopbackSettings->PreferredIpAddress.Address = wsl::windows::common::string::StringToSockAddrInet(clientIp); |
| 428 | loopbackSettings->PreferredIpAddress.AddressString = clientIp; |
| 429 | loopbackSettings->PreferredIpAddress.PrefixLength = 28; |
| 430 | loopbackSettings->Routes.emplace(EndpointRoute::DefaultRoute(AF_INET, wsl::windows::common::string::StringToSockAddrInet(L"169.254.73.249"))); |
| 431 | m_localhostAdapterId = m_guestDeviceManager->AddVirtioNetDevice( |
| 432 | c_loopbackDeviceName, BuildVirtioNetConfig(loopbackSettings, false, c_gatewayMacAddress), {}, m_userToken.get()); |
| 433 | |
| 434 | // The loopback gateway (see LX_INIT_IPV4_LOOPBACK_GATEWAY_ADDRESS) is 169.254.73.249, so use a /28 subnet |
| 435 | // that includes both the client and gateway addresses. |
| 436 | // N.B. The MAC address is advertised with the virtio device so doesn't need to be explicitly set. |
| 437 | hns::HNSEndpoint endpointProperties; |
| 438 | endpointProperties.ID = m_localhostAdapterId.value(); |
| 439 | endpointProperties.IPAddress = L"169.254.73.250"; |
| 440 | endpointProperties.PrefixLength = 28; |
| 441 | endpointProperties.PortFriendlyName = c_loopbackDeviceName; |
| 442 | m_gnsChannel.SendEndpointState(endpointProperties); |
| 443 | |
| 444 | hns::CreateDeviceRequest createLoopbackDevice; |
| 445 | createLoopbackDevice.deviceName = c_loopbackDeviceName; |
| 446 | createLoopbackDevice.type = hns::DeviceType::Loopback; |
| 447 | createLoopbackDevice.lowerEdgeAdapterId = m_localhostAdapterId.value(); |
| 448 | |
| 449 | // ipv6 duplicate address detection (DAD) breaks the ipv6 localhost relay since we can't predict the address before the guest tells us about it. |
| 450 | createLoopbackDevice.flags = hns::CreateDeviceFlags::DisableDAD; |
| 451 | constexpr auto loopbackType = GnsMessageType(createLoopbackDevice); |
| 452 | m_gnsChannel.SendNetworkDeviceMessage(loopbackType, ToJsonW(createLoopbackDevice).c_str()); |
| 453 | } |
| 454 | |
| 455 | void ConsommeNetworking::SendDefaultRoute(const std::wstring& gateway, hns::ModifyRequestType requestType) |
| 456 | { |
| 457 | if (gateway.empty() || !m_adapterId.has_value()) |
| 458 | { |
| 459 | return; |
| 460 | } |
| 461 | |
| 462 | wsl::shared::hns::Route route; |
| 463 | route.NextHop = gateway; |
| 464 | route.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_PREFIX; |
| 465 | route.Family = AF_INET; |
| 466 | |
| 467 | hns::ModifyGuestEndpointSettingRequest<hns::Route> request; |
| 468 | request.RequestType = requestType; |
| 469 | request.ResourceType = hns::GuestEndpointResourceType::Route; |
| 470 | request.Settings = route; |
| 471 | m_gnsChannel.SendHnsNotification(ToJsonW(request).c_str(), m_adapterId.value()); |
| 472 | } |
| 473 | |
| 474 | void ConsommeNetworking::UpdateDefaultRoute(const std::wstring& gateway) |
| 475 | { |
| 476 | if (gateway == m_trackedDefaultRoute || !m_adapterId.has_value()) |
| 477 | { |
| 478 | return; |
| 479 | } |
| 480 | |
| 481 | SendDefaultRoute(m_trackedDefaultRoute, hns::ModifyRequestType::Remove); |
| 482 | m_trackedDefaultRoute = gateway; |
| 483 | SendDefaultRoute(gateway, hns::ModifyRequestType::Add); |
| 484 | } |
| 485 | |
| 486 | void ConsommeNetworking::UpdateDnsSettings(const networking::DnsInfo& dns) |
| 487 | { |
| 488 | if (dns == m_trackedDnsSettings || !m_adapterId.has_value()) |
| 489 | { |
| 490 | return; |
| 491 | } |
| 492 | |
| 493 | m_trackedDnsSettings = dns; |
| 494 | |
| 495 | hns::ModifyGuestEndpointSettingRequest<hns::DNS> notification{}; |
| 496 | notification.RequestType = hns::ModifyRequestType::Update; |
| 497 | notification.ResourceType = hns::GuestEndpointResourceType::DNS; |
| 498 | notification.Settings = networking::BuildDnsNotification(dns, m_dnsOptions); |
| 499 | m_gnsChannel.SendHnsNotification(ToJsonW(notification).c_str(), m_adapterId.value()); |
| 500 | } |
| 501 | |
| 502 | void ConsommeNetworking::UpdateIpv4Address(const networking::EndpointIpAddress& ipAddress) |
| 503 | { |
| 504 | if (ipAddress == m_trackedIpv4Address || ipAddress.AddressString.empty() || !m_adapterId.has_value()) |
| 505 | { |
| 506 | return; |
| 507 | } |
| 508 | |
| 509 | m_trackedIpv4Address = ipAddress; |
| 510 | |
| 511 | // N.B. SendEndpointState triggers SetAdapterConfiguration on the Linux side |
| 512 | // which brings the interface UP and configures the full adapter state. |
| 513 | hns::HNSEndpoint endpointProperties; |
| 514 | endpointProperties.ID = m_adapterId.value(); |
| 515 | endpointProperties.IPAddress = ipAddress.AddressString; |
| 516 | endpointProperties.PrefixLength = ipAddress.PrefixLength; |
| 517 | m_gnsChannel.SendEndpointState(endpointProperties); |
| 518 | } |
| 519 | |
| 520 | void ConsommeNetworking::SendIpv6Address(const networking::EndpointIpAddress& ipAddress, hns::ModifyRequestType requestType) |
| 521 | { |
| 522 | WI_ASSERT(WI_IsFlagSet(m_flags, ConsommeNetworkingFlags::Ipv6)); |
| 523 | |
| 524 | if (ipAddress.AddressString.empty() || !m_adapterId.has_value()) |
| 525 | { |
| 526 | return; |
| 527 | } |
| 528 | |
| 529 | // The HNSEndpoint schema doesn't support IPv6 addresses, so use ModifyGuestEndpointSettingRequest. |
| 530 | hns::ModifyGuestEndpointSettingRequest<hns::IPAddress> request; |
| 531 | request.RequestType = requestType; |
| 532 | request.ResourceType = hns::GuestEndpointResourceType::IPAddress; |
| 533 | request.Settings.Address = ipAddress.AddressString; |
| 534 | request.Settings.Family = ipAddress.Address.si_family; |
| 535 | request.Settings.OnLinkPrefixLength = ipAddress.PrefixLength; |
| 536 | request.Settings.PreferredLifetime = ULONG_MAX; |
| 537 | m_gnsChannel.SendHnsNotification(ToJsonW(request).c_str(), m_adapterId.value()); |
| 538 | } |
| 539 | |
| 540 | void ConsommeNetworking::UpdateIpv6Address(const networking::EndpointIpAddress& ipAddress) |
| 541 | { |
| 542 | WI_ASSERT(WI_IsFlagSet(m_flags, ConsommeNetworkingFlags::Ipv6)); |
| 543 | |
| 544 | if (ipAddress == m_trackedIpv6Address || !m_adapterId.has_value()) |
| 545 | { |
| 546 | return; |
| 547 | } |
| 548 | |
| 549 | SendIpv6Address(m_trackedIpv6Address, hns::ModifyRequestType::Remove); |
| 550 | m_trackedIpv6Address = ipAddress; |
| 551 | SendIpv6Address(ipAddress, hns::ModifyRequestType::Add); |
| 552 | } |
| 553 | |
| 554 | void ConsommeNetworking::UpdateMtu(std::optional<ULONG> mtu) |
| 555 | { |
| 556 | if (!mtu || mtu.value() == m_networkMtu || !m_adapterId.has_value()) |
| 557 | { |
| 558 | return; |
| 559 | } |
| 560 | |
| 561 | m_networkMtu = mtu.value(); |
| 562 | |
| 563 | hns::ModifyGuestEndpointSettingRequest<hns::NetworkInterface> notification{}; |
| 564 | notification.ResourceType = hns::GuestEndpointResourceType::Interface; |
| 565 | notification.RequestType = hns::ModifyRequestType::Update; |
| 566 | notification.Settings.Connected = true; |
| 567 | notification.Settings.NlMtu = m_networkMtu; |
| 568 | m_gnsChannel.SendHnsNotification(ToJsonW(notification).c_str(), m_adapterId.value()); |
| 569 | } |