| 1 | // Copyright (C) Microsoft Corporation. All rights reserved. |
| 2 | |
| 3 | #pragma once |
| 4 | #include <algorithm> |
| 5 | #include <set> |
| 6 | #include <string> |
| 7 | |
| 8 | #include <windows.h> |
| 9 | #include <mstcpip.h> |
| 10 | #include <ws2ipdef.h> |
| 11 | #include <netioapi.h> |
| 12 | |
| 13 | #include "hcs.hpp" |
| 14 | #include "lxinitshared.h" |
| 15 | #include "Stringify.h" |
| 16 | #include "stringshared.h" |
| 17 | #include "WslCoreNetworkingSupport.h" |
| 18 | #include "hns_schema.h" |
| 19 | |
| 20 | namespace wsl::core::networking { |
| 21 | |
| 22 | constexpr auto AddEndpointRetryPeriod = std::chrono::milliseconds(100); |
| 23 | constexpr auto AddEndpointRetryTimeout = std::chrono::seconds(3); |
| 24 | constexpr auto AddEndpointRetryPredicate = [] { |
| 25 | // Don't retry if ModifyComputeSystem fails with: |
| 26 | // HCN_E_ENDPOINT_NOT_FOUND - indicates that the underlying network object was deleted. |
| 27 | // HCN_E_ENDPOINT_ALREADY_ATTACHED - occurs when HNS was restarted before the endpoints were removed. |
| 28 | // VM_E_INVALID_STATE - occurs when the VM has been terminated. |
| 29 | const auto result = wil::ResultFromCaughtException(); |
| 30 | return result != HCN_E_ENDPOINT_NOT_FOUND && result != HCN_E_ENDPOINT_ALREADY_ATTACHED && result != VM_E_INVALID_STATE; |
| 31 | }; |
| 32 | |
| 33 | struct EndpointIpAddress |
| 34 | { |
| 35 | SOCKADDR_INET Address{}; |
| 36 | std::wstring AddressString{}; |
| 37 | unsigned char PrefixLength = 0; |
| 38 | unsigned int PrefixOrigin = 0; |
| 39 | unsigned int SuffixOrigin = 0; |
| 40 | |
| 41 | // The following field can be changed from a const iterator in SyncIpStateWithLinux - that's why it's marked mutable. |
| 42 | mutable unsigned int PreferredLifetime = 0; |
| 43 | |
| 44 | EndpointIpAddress() = default; |
| 45 | ~EndpointIpAddress() noexcept = default; |
| 46 | |
| 47 | EndpointIpAddress(EndpointIpAddress&&) = default; |
| 48 | EndpointIpAddress& operator=(EndpointIpAddress&&) = default; |
| 49 | EndpointIpAddress(const EndpointIpAddress&) = default; |
| 50 | EndpointIpAddress& operator=(const EndpointIpAddress&) = default; |
| 51 | |
| 52 | explicit EndpointIpAddress(const MIB_UNICASTIPADDRESS_ROW& AddressRow) : |
| 53 | Address(AddressRow.Address), |
| 54 | AddressString(windows::common::string::SockAddrInetToWstring(AddressRow.Address)), |
| 55 | PrefixLength(AddressRow.OnLinkPrefixLength), |
| 56 | PrefixOrigin(AddressRow.PrefixOrigin), |
| 57 | SuffixOrigin(AddressRow.SuffixOrigin), |
| 58 | // We treat the preferred lifetime field as effective DAD state - 0 is not preferred, anything else is preferred. |
| 59 | // We do this for convenience, as we can't directly set the DAD state of an address into the guest, but we |
| 60 | // we can set an address's preferred lifetime (in Linux, at least). |
| 61 | PreferredLifetime(AddressRow.DadState == IpDadStatePreferred ? 0xFFFFFFFF : 0) |
| 62 | { |
| 63 | } |
| 64 | |
| 65 | // operator== is deliberately not comparing PreferredLifetime (DAD state) for equality - only the address portion |
| 66 | bool operator==(const EndpointIpAddress& rhs) const noexcept |
| 67 | { |
| 68 | return Address == rhs.Address && PrefixLength == rhs.PrefixLength; |
| 69 | } |
| 70 | |
| 71 | bool operator<(const EndpointIpAddress& rhs) const noexcept |
| 72 | { |
| 73 | if (Address == rhs.Address) |
| 74 | { |
| 75 | return PrefixLength < rhs.PrefixLength; |
| 76 | } |
| 77 | return Address < rhs.Address; |
| 78 | } |
| 79 | |
| 80 | void Clear() noexcept |
| 81 | { |
| 82 | Address = {}; |
| 83 | AddressString.clear(); |
| 84 | PrefixLength = 0; |
| 85 | PrefixOrigin = 0; |
| 86 | SuffixOrigin = 0; |
| 87 | } |
| 88 | |
| 89 | std::wstring GetPrefix() const |
| 90 | { |
| 91 | SOCKADDR_INET address{Address}; |
| 92 | unsigned char* addressPointer{nullptr}; |
| 93 | |
| 94 | if (Address.si_family == AF_INET) |
| 95 | { |
| 96 | addressPointer = reinterpret_cast<unsigned char*>(&address.Ipv4.sin_addr); |
| 97 | } |
| 98 | else if (Address.si_family == AF_INET6) |
| 99 | { |
| 100 | addressPointer = address.Ipv6.sin6_addr.u.Byte; |
| 101 | } |
| 102 | else |
| 103 | { |
| 104 | return L""; |
| 105 | } |
| 106 | |
| 107 | constexpr int c_numBitsPerByte = 8; |
| 108 | for (int i = 0, currPrefixLength = PrefixLength; i < INET_ADDR_LENGTH(Address.si_family); i++, currPrefixLength -= c_numBitsPerByte) |
| 109 | { |
| 110 | if (currPrefixLength < c_numBitsPerByte) |
| 111 | { |
| 112 | const int bitShiftAmt = c_numBitsPerByte - std::max(currPrefixLength, 0); |
| 113 | addressPointer[i] &= (0xFF >> bitShiftAmt) << bitShiftAmt; |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | const auto addressString = windows::common::string::SockAddrInetToWstring(address); |
| 118 | WI_ASSERT(!addressString.empty()); |
| 119 | if (addressString.empty()) |
| 120 | { |
| 121 | // just return an empty string if we have a bad address |
| 122 | return addressString; |
| 123 | } |
| 124 | |
| 125 | return std::format(L"{}/{}", addressString, PrefixLength); |
| 126 | } |
| 127 | |
| 128 | std::wstring GetIpv4BroadcastMask() const |
| 129 | { |
| 130 | // start with all bits set, then shift off the prefix |
| 131 | ULONG prefixMask{0xffffffff}; |
| 132 | prefixMask <<= PrefixLength; |
| 133 | prefixMask >>= PrefixLength; |
| 134 | |
| 135 | SOCKADDR_INET address{Address}; |
| 136 | // flip to host-order, then apply the mask |
| 137 | ULONG hostOrder = ntohl(address.Ipv4.sin_addr.S_un.S_addr); |
| 138 | hostOrder |= prefixMask; |
| 139 | address.Ipv4.sin_addr.S_un.S_addr = htonl(hostOrder); |
| 140 | |
| 141 | return windows::common::string::SockAddrInetToWstring(address); |
| 142 | } |
| 143 | |
| 144 | bool IsPreferred() const noexcept |
| 145 | { |
| 146 | return PreferredLifetime > 0; |
| 147 | } |
| 148 | |
| 149 | bool IsLinkLocal() const |
| 150 | { |
| 151 | return (Address.si_family == AF_INET && IN4_IS_ADDR_LINKLOCAL(&Address.Ipv4.sin_addr)) || |
| 152 | (Address.si_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&Address.Ipv6.sin6_addr)); |
| 153 | } |
| 154 | }; |
| 155 | |
| 156 | struct EndpointRoute |
| 157 | { |
| 158 | ADDRESS_FAMILY Family = AF_INET; |
| 159 | IP_ADDRESS_PREFIX DestinationPrefix{}; |
| 160 | std::wstring DestinationPrefixString{}; |
| 161 | SOCKADDR_INET NextHop{}; |
| 162 | std::wstring NextHopString{}; |
| 163 | unsigned char SitePrefixLength = 0; |
| 164 | unsigned int Metric = 0; |
| 165 | bool IsAutoGeneratedPrefixRoute = false; |
| 166 | |
| 167 | EndpointRoute() = default; |
| 168 | ~EndpointRoute() noexcept = default; |
| 169 | |
| 170 | EndpointRoute(EndpointRoute&&) = default; |
| 171 | EndpointRoute& operator=(EndpointRoute&&) = default; |
| 172 | EndpointRoute(const EndpointRoute&) = default; |
| 173 | EndpointRoute& operator=(const EndpointRoute&) = default; |
| 174 | |
| 175 | // Build a default route (0.0.0.0/0 or ::/0) with the given next hop. |
| 176 | static EndpointRoute DefaultRoute(ADDRESS_FAMILY family, const SOCKADDR_INET& nextHop) |
| 177 | { |
| 178 | EndpointRoute route{}; |
| 179 | route.Family = family; |
| 180 | route.DestinationPrefix.PrefixLength = 0; |
| 181 | if (family == AF_INET) |
| 182 | { |
| 183 | IN4ADDR_SETANY(&route.DestinationPrefix.Prefix.Ipv4); |
| 184 | route.DestinationPrefixString = LX_INIT_UNSPECIFIED_ADDRESS; |
| 185 | } |
| 186 | else |
| 187 | { |
| 188 | IN6ADDR_SETANY(&route.DestinationPrefix.Prefix.Ipv6); |
| 189 | route.DestinationPrefixString = LX_INIT_UNSPECIFIED_V6_ADDRESS; |
| 190 | } |
| 191 | route.NextHop = nextHop; |
| 192 | route.NextHopString = windows::common::string::SockAddrInetToWstring(nextHop); |
| 193 | return route; |
| 194 | } |
| 195 | |
| 196 | EndpointRoute(const MIB_IPFORWARD_ROW2& RouteRow) : |
| 197 | Family(RouteRow.NextHop.si_family), |
| 198 | DestinationPrefix(RouteRow.DestinationPrefix), |
| 199 | DestinationPrefixString(windows::common::string::SockAddrInetToWstring(RouteRow.DestinationPrefix.Prefix)), |
| 200 | NextHop(RouteRow.NextHop), |
| 201 | NextHopString(windows::common::string::SockAddrInetToWstring(RouteRow.NextHop)), |
| 202 | SitePrefixLength(RouteRow.SitePrefixLength), |
| 203 | Metric(RouteRow.Metric) |
| 204 | { |
| 205 | } |
| 206 | |
| 207 | unsigned char GetMaxPrefixLength() const |
| 208 | { |
| 209 | return (Family == AF_INET) ? 32 : 128; |
| 210 | } |
| 211 | |
| 212 | std::wstring GetFullDestinationPrefix() const |
| 213 | { |
| 214 | return std::format(L"{}/{}", DestinationPrefixString, static_cast<unsigned int>(DestinationPrefix.PrefixLength)); |
| 215 | } |
| 216 | |
| 217 | bool IsNextHopOnlink() const noexcept |
| 218 | { |
| 219 | return (Family == AF_INET && NextHopString == LX_INIT_UNSPECIFIED_ADDRESS) || |
| 220 | (Family == AF_INET6 && NextHopString == LX_INIT_UNSPECIFIED_V6_ADDRESS); |
| 221 | } |
| 222 | |
| 223 | bool IsDefault() const noexcept |
| 224 | { |
| 225 | return DestinationPrefix.PrefixLength == 0 && ((Family == AF_INET && DestinationPrefixString == LX_INIT_UNSPECIFIED_ADDRESS) || |
| 226 | (Family == AF_INET6 && DestinationPrefixString == LX_INIT_UNSPECIFIED_V6_ADDRESS)); |
| 227 | } |
| 228 | |
| 229 | bool IsUnicastAddressRoute() const noexcept |
| 230 | { |
| 231 | return (Family == AF_INET && DestinationPrefix.PrefixLength == 32) || (Family == AF_INET6 && DestinationPrefix.PrefixLength == 128); |
| 232 | } |
| 233 | |
| 234 | std::wstring ToString() const |
| 235 | { |
| 236 | return std::format(L"{}=>{} [metric {}]", GetFullDestinationPrefix(), NextHopString, Metric); |
| 237 | } |
| 238 | |
| 239 | bool operator==(const EndpointRoute& rhs) const noexcept |
| 240 | { |
| 241 | return Family == rhs.Family && DestinationPrefix.PrefixLength == rhs.DestinationPrefix.PrefixLength && |
| 242 | DestinationPrefix.Prefix == rhs.DestinationPrefix.Prefix && NextHop == rhs.NextHop && |
| 243 | SitePrefixLength == rhs.SitePrefixLength && Metric == rhs.Metric; |
| 244 | } |
| 245 | |
| 246 | bool operator!=(const EndpointRoute& other) const |
| 247 | { |
| 248 | return !(*this == other); |
| 249 | } |
| 250 | |
| 251 | // sort by family, then by next-hop (on-link routes first), then by prefix, then by metric |
| 252 | bool operator<(const EndpointRoute& rhs) const noexcept |
| 253 | { |
| 254 | if (Family == rhs.Family) |
| 255 | { |
| 256 | if (NextHop == rhs.NextHop) |
| 257 | { |
| 258 | if (DestinationPrefix.Prefix == rhs.DestinationPrefix.Prefix) |
| 259 | { |
| 260 | if (DestinationPrefix.PrefixLength == rhs.DestinationPrefix.PrefixLength) |
| 261 | { |
| 262 | if (Metric == rhs.Metric) |
| 263 | { |
| 264 | return SitePrefixLength < rhs.SitePrefixLength; |
| 265 | } |
| 266 | return Metric < rhs.Metric; |
| 267 | } |
| 268 | return DestinationPrefix.PrefixLength < rhs.DestinationPrefix.PrefixLength; |
| 269 | } |
| 270 | return DestinationPrefix.Prefix < rhs.DestinationPrefix.Prefix; |
| 271 | } |
| 272 | return NextHop < rhs.NextHop; |
| 273 | } |
| 274 | return Family < rhs.Family; |
| 275 | } |
| 276 | }; |
| 277 | |
| 278 | struct NetworkSettings |
| 279 | { |
| 280 | NetworkSettings() = default; |
| 281 | |
| 282 | NetworkSettings( |
| 283 | const GUID& interfaceGuid, |
| 284 | EndpointIpAddress preferredIpAddress, |
| 285 | EndpointIpAddress preferredIpv6Address, |
| 286 | EndpointRoute gateway, |
| 287 | EndpointRoute v6Gateway, |
| 288 | std::wstring macAddress, |
| 289 | uint32_t interfaceIndex, |
| 290 | uint32_t mediaType) : |
| 291 | InterfaceGuid(interfaceGuid), |
| 292 | PreferredIpAddress(std::move(preferredIpAddress)), |
| 293 | PreferredIpv6Address(std::move(preferredIpv6Address)), |
| 294 | MacAddress(std::move(macAddress)), |
| 295 | InterfaceIndex(interfaceIndex), |
| 296 | InterfaceType(mediaType) |
| 297 | { |
| 298 | // Only insert routes that have a valid next hop. A default-constructed or empty |
| 299 | // EndpointRoute indicates no gateway was found for that address family. |
| 300 | if (!gateway.NextHopString.empty()) |
| 301 | { |
| 302 | Routes.emplace(std::move(gateway)); |
| 303 | } |
| 304 | |
| 305 | if (!v6Gateway.NextHopString.empty()) |
| 306 | { |
| 307 | Routes.emplace(std::move(v6Gateway)); |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | GUID InterfaceGuid{}; |
| 312 | EndpointIpAddress PreferredIpAddress{}; |
| 313 | EndpointIpAddress PreferredIpv6Address{}; |
| 314 | std::set<EndpointIpAddress> IpAddresses{}; // Does not include PreferredIpAddress or PreferredIpv6Address. |
| 315 | std::set<EndpointRoute> Routes{}; |
| 316 | std::wstring MacAddress; |
| 317 | IF_INDEX InterfaceIndex = 0; |
| 318 | IFTYPE InterfaceType = 0; |
| 319 | ULONG IPv4InterfaceMtu = 0; |
| 320 | ULONG IPv6InterfaceMtu = 0; |
| 321 | // some interfaces will only have an IPv4 or IPv6 interface |
| 322 | std::optional<ULONG> IPv4InterfaceMetric = 0; |
| 323 | std::optional<ULONG> IPv6InterfaceMetric = 0; |
| 324 | bool IsHidden = false; |
| 325 | bool IsConnected = false; |
| 326 | bool IsMetered = false; |
| 327 | bool DisableIpv4DefaultRoutes = false; |
| 328 | bool DisableIpv6DefaultRoutes = false; |
| 329 | bool PendingUpdateToReconnectForMetered = false; |
| 330 | bool PendingIPInterfaceUpdate = false; |
| 331 | |
| 332 | auto operator<=>(const NetworkSettings&) const = default; |
| 333 | |
| 334 | // Returns the next-hop string of the first default route matching the given address family. |
| 335 | std::wstring GetBestGatewayAddressString(ADDRESS_FAMILY family = AF_INET) const |
| 336 | { |
| 337 | const auto& unspecified = (family == AF_INET) ? LX_INIT_UNSPECIFIED_ADDRESS : LX_INIT_UNSPECIFIED_V6_ADDRESS; |
| 338 | for (const auto& route : Routes) |
| 339 | { |
| 340 | if (route.Family == family && route.DestinationPrefix.PrefixLength == 0 && route.DestinationPrefixString == unspecified) |
| 341 | { |
| 342 | return route.NextHopString; |
| 343 | } |
| 344 | } |
| 345 | |
| 346 | return {}; |
| 347 | } |
| 348 | |
| 349 | // Returns the next-hop address of the first default route matching the given address family. |
| 350 | SOCKADDR_INET GetBestGatewayAddress(ADDRESS_FAMILY family = AF_INET) const |
| 351 | { |
| 352 | const auto& unspecified = (family == AF_INET) ? LX_INIT_UNSPECIFIED_ADDRESS : LX_INIT_UNSPECIFIED_V6_ADDRESS; |
| 353 | for (const auto& route : Routes) |
| 354 | { |
| 355 | if (route.Family == family && route.DestinationPrefix.PrefixLength == 0 && route.DestinationPrefixString == unspecified) |
| 356 | { |
| 357 | return route.NextHop; |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | return {}; |
| 362 | } |
| 363 | |
| 364 | std::wstring IpAddressesString() const |
| 365 | { |
| 366 | return std::accumulate(std::begin(IpAddresses), std::end(IpAddresses), std::wstring{}, [](const std::wstring& prev, const auto& addr) { |
| 367 | return addr.AddressString + (prev.empty() ? L"" : L"," + prev); |
| 368 | }); |
| 369 | } |
| 370 | |
| 371 | std::wstring RoutesString() const |
| 372 | { |
| 373 | return std::accumulate(std::begin(Routes), std::end(Routes), std::wstring{}, [](const std::wstring& prev, const EndpointRoute& route) { |
| 374 | return route.ToString() + (prev.empty() ? L"" : L"," + prev); |
| 375 | }); |
| 376 | } |
| 377 | |
| 378 | // will return ULONG_MAX if there's no configured MTU |
| 379 | ULONG GetEffectiveMtu() const noexcept |
| 380 | { |
| 381 | return std::min(IPv4InterfaceMtu > 0 ? IPv4InterfaceMtu : ULONG_MAX, IPv6InterfaceMtu > 0 ? IPv6InterfaceMtu : ULONG_MAX); |
| 382 | } |
| 383 | |
| 384 | // will return zero if there's no configured metric |
| 385 | ULONG GetMinimumMetric() const noexcept |
| 386 | { |
| 387 | if (!IPv4InterfaceMetric.has_value() && !IPv6InterfaceMetric.has_value()) |
| 388 | { |
| 389 | return 0; |
| 390 | } |
| 391 | if (!IPv4InterfaceMetric.has_value()) |
| 392 | { |
| 393 | return IPv6InterfaceMetric.value(); |
| 394 | } |
| 395 | if (!IPv6InterfaceMetric.has_value()) |
| 396 | { |
| 397 | return IPv4InterfaceMetric.value(); |
| 398 | } |
| 399 | return std::min(IPv4InterfaceMetric.value(), IPv6InterfaceMetric.value()); |
| 400 | } |
| 401 | }; |
| 402 | |
| 403 | std::shared_ptr<NetworkSettings> GetEndpointSettings(const wsl::shared::hns::HNSEndpoint& properties); |
| 404 | std::shared_ptr<NetworkSettings> GetHostEndpointSettings(); |
| 405 | |
| 406 | #define TRACE_NETWORKSETTINGS_OBJECT(settings) \ |
| 407 | TraceLoggingValue((settings)->InterfaceGuid, "interfaceGuid"), TraceLoggingValue((settings)->InterfaceIndex, "interfaceIndex"), \ |
| 408 | TraceLoggingValue((settings)->InterfaceType, "interfaceType"), \ |
| 409 | TraceLoggingValue((settings)->IsConnected, "isConnected"), TraceLoggingValue((settings)->IsMetered, "isMetered"), \ |
| 410 | TraceLoggingValue((settings)->GetBestGatewayAddressString().c_str(), "bestGatewayAddress"), \ |
| 411 | TraceLoggingValue((settings)->PreferredIpAddress.AddressString.c_str(), "preferredIpAddress"), \ |
| 412 | TraceLoggingValue((settings)->PreferredIpAddress.PrefixLength, "preferredIpAddressPrefixLength"), \ |
| 413 | TraceLoggingValue((settings)->PreferredIpv6Address.AddressString.c_str(), "preferredIpv6Address"), \ |
| 414 | TraceLoggingValue((settings)->PreferredIpv6Address.PrefixLength, "preferredIpv6AddressPrefixLength"), \ |
| 415 | TraceLoggingValue((settings)->GetBestGatewayAddressString(AF_INET6).c_str(), "bestGatewayV6Address"), \ |
| 416 | TraceLoggingValue((settings)->IpAddressesString().c_str(), "ipAddresses"), \ |
| 417 | TraceLoggingValue((settings)->RoutesString().c_str(), "routes"), \ |
| 418 | TraceLoggingValue((settings)->MacAddress.c_str(), "macAddress"), \ |
| 419 | TraceLoggingValue((settings)->IPv4InterfaceMtu, "IPv4InterfaceMtu"), \ |
| 420 | TraceLoggingValue((settings)->IPv6InterfaceMtu, "IPv6InterfaceMtu"), \ |
| 421 | TraceLoggingValue((settings)->IPv4InterfaceMetric.value_or(0xffffffff), "IPv4InterfaceMetric"), \ |
| 422 | TraceLoggingValue((settings)->IPv6InterfaceMetric.value_or(0xffffffff), "IPv6InterfaceMetric"), \ |
| 423 | TraceLoggingValue((settings)->PendingIPInterfaceUpdate, "PendingIPInterfaceUpdate"), \ |
| 424 | TraceLoggingValue((settings)->PendingUpdateToReconnectForMetered, "PendingUpdateToReconnectForMetered") |
| 425 | |
| 426 | } // namespace wsl::core::networking |