| 1 | // Copyright (C) Microsoft Corporation. All rights reserved. |
| 2 | |
| 3 | #include <iostream> |
| 4 | #include <locale> |
| 5 | #include <regex> |
| 6 | #include <filesystem> |
| 7 | #include <format> |
| 8 | #include <fstream> |
| 9 | #include "address.h" |
| 10 | #include "common.h" |
| 11 | #include "GnsEngine.h" |
| 12 | #include "util.h" |
| 13 | #include "Utils.h" |
| 14 | #include "lxinitshared.h" |
| 15 | #include "stringshared.h" |
| 16 | |
| 17 | using wsl::shared::hns::GuestEndpointResourceType; |
| 18 | using wsl::shared::hns::ModifyGuestEndpointSettingRequest; |
| 19 | using wsl::shared::hns::ModifyRequestType; |
| 20 | |
| 21 | constexpr auto c_interfaceLookupTimeout = std::chrono::seconds(30); |
| 22 | constexpr auto c_interfaceLookupRetryPeriod = std::chrono::milliseconds(100); |
| 23 | constexpr auto c_ipStrings = {"ip", "ip6"}; |
| 24 | |
| 25 | const char* c_loopbackInterfaceName = "lo"; |
| 26 | |
| 27 | GnsEngine::GnsEngine( |
| 28 | wsl::shared::SocketChannel& channel, |
| 29 | const NotificationRoutine& notificationRoutine, |
| 30 | const StatusRoutine& statusRoutine, |
| 31 | NetworkManager& manager, |
| 32 | std::optional<int> dnsTunnelingFd, |
| 33 | const std::string& dnsTunnelingIpAddress) : |
| 34 | channel(channel), notificationRoutine(notificationRoutine), statusRoutine(statusRoutine), manager(manager) |
| 35 | { |
| 36 | if (dnsTunnelingFd.has_value()) |
| 37 | { |
| 38 | // Add the IP address to the loopback interface, to be used by the DNS tunneling listener. |
| 39 | // Note: Linux allows IPv4 addresses that are not in the range 127.0.0.0/8 to be added to the loopback interface. |
| 40 | auto loInterface = Interface::Open(c_loopbackInterfaceName); |
| 41 | Address address{AF_INET, 32, dnsTunnelingIpAddress}; |
| 42 | manager.ModifyAddress(loInterface, address, Operation::Create); |
| 43 | |
| 44 | dnsTunnelingManager.emplace(dnsTunnelingFd.value(), dnsTunnelingIpAddress); |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | Interface GnsEngine::OpenAdapterImpl(const GUID& id) |
| 49 | { |
| 50 | std::string interfaceName; |
| 51 | for (const auto& e : std::filesystem::directory_iterator("/sys/class/net/")) |
| 52 | { |
| 53 | auto adapterId = GetAdapterId(e.path()); |
| 54 | if (adapterId.has_value() && adapterId.value() == id) |
| 55 | { |
| 56 | interfaceName = e.path().filename().string(); |
| 57 | // Special case _wlanxx interfaces: look for the wlanxx version instead. |
| 58 | if (interfaceName.compare(0, 5, "_wlan") == 0) |
| 59 | { |
| 60 | continue; |
| 61 | } |
| 62 | |
| 63 | break; |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | if (!interfaceName.empty()) |
| 68 | { |
| 69 | GNS_LOG_INFO( |
| 70 | "Found an interface matching the GUID {}, with name {}", |
| 71 | wsl::shared::string::GuidToString<char>(id).c_str(), |
| 72 | interfaceName.c_str()); |
| 73 | return Interface::Open(interfaceName); |
| 74 | } |
| 75 | |
| 76 | throw RuntimeErrorWithSourceLocation(std::format("Couldn't find an adapter for id: {}", wsl::shared::string::GuidToString<char>(id))); |
| 77 | } |
| 78 | |
| 79 | Interface GnsEngine::OpenAdapter(const GUID& id) |
| 80 | { |
| 81 | return wsl::shared::retry::RetryWithTimeout<Interface>([&]() { return OpenAdapterImpl(id); }, c_interfaceLookupRetryPeriod, c_interfaceLookupTimeout); |
| 82 | } |
| 83 | |
| 84 | Interface GnsEngine::OpenInterfaceImpl(const std::string& deviceName) |
| 85 | { |
| 86 | try |
| 87 | { |
| 88 | return Interface::Open(deviceName); |
| 89 | } |
| 90 | catch (const std::exception& e) |
| 91 | { |
| 92 | throw RuntimeErrorWithSourceLocation(std::format("Failed to open interface with device name: {}", deviceName), e); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | Interface GnsEngine::OpenInterface(const std::string& deviceName) |
| 97 | { |
| 98 | return wsl::shared::retry::RetryWithTimeout<Interface>( |
| 99 | [&]() { return OpenInterfaceImpl(deviceName); }, c_interfaceLookupRetryPeriod, c_interfaceLookupTimeout); |
| 100 | } |
| 101 | |
| 102 | std::optional<GUID> GnsEngine::GetAdapterId(const std::string& path) |
| 103 | { |
| 104 | // Sample symlink: |
| 105 | // /sys/class/net/eth0/device -> ../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0004:00/VMBUS:00/ebfda100-7464-4629-9da5-12de5470cb4f |
| 106 | |
| 107 | try |
| 108 | { |
| 109 | auto symlink = std::filesystem::read_symlink(path); |
| 110 | const std::string adapterName = symlink.filename(); |
| 111 | if (adapterName.size() > 3 && adapterName.compare(0, 4, "wlan") == 0) |
| 112 | { |
| 113 | symlink = symlink.parent_path().parent_path(); |
| 114 | } |
| 115 | auto device = symlink.parent_path().parent_path(); |
| 116 | std::string deviceGuid = device.filename(); |
| 117 | if (deviceGuid.size() > 6 && deviceGuid.compare(0, 6, "virtio") == 0) |
| 118 | { |
| 119 | deviceGuid = device.parent_path().parent_path().parent_path().filename(); |
| 120 | } |
| 121 | |
| 122 | return wsl::shared::string::ToGuid(deviceGuid); |
| 123 | } |
| 124 | catch (...) |
| 125 | { |
| 126 | return {}; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | Interface GnsEngine::OpenInterfaceOrAdapter(const std::wstring& nameOrId) |
| 131 | { |
| 132 | if (!nameOrId.empty() && nameOrId[0] == L'{') |
| 133 | { |
| 134 | auto id = wsl::shared::string::ToGuid(nameOrId); |
| 135 | if (!id.has_value()) |
| 136 | { |
| 137 | THROW_ERRNO(EINVAL); |
| 138 | } |
| 139 | |
| 140 | return OpenAdapter(id.value()); |
| 141 | } |
| 142 | else |
| 143 | { |
| 144 | return OpenInterface(wsl::shared::string::WideToMultiByte(nameOrId)); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | void GnsEngine::ProcessNotification(const nlohmann::json& payload, Interface& interface) |
| 149 | { |
| 150 | using namespace std::placeholders; |
| 151 | |
| 152 | if (!payload.contains("ResourceType")) |
| 153 | { |
| 154 | throw RuntimeErrorWithSourceLocation("Json is missing ResourceType"); |
| 155 | } |
| 156 | |
| 157 | switch (payload["ResourceType"].get<GuestEndpointResourceType>()) |
| 158 | { |
| 159 | case GuestEndpointResourceType::Route: |
| 160 | GNS_LOG_INFO("GuestEndpointResourceType::Route for interfaceName {}", interface.Name().c_str()); |
| 161 | ProcessNotificationImpl(interface, payload, &GnsEngine::ProcessRouteChange); |
| 162 | break; |
| 163 | |
| 164 | case GuestEndpointResourceType::IPAddress: |
| 165 | GNS_LOG_INFO("GuestEndpointResourceType::IPAddress for interfaceName {}", interface.Name().c_str()); |
| 166 | ProcessNotificationImpl(interface, payload, &GnsEngine::ProcessIpAddressChange); |
| 167 | break; |
| 168 | |
| 169 | case GuestEndpointResourceType::MacAddress: |
| 170 | GNS_LOG_INFO("GuestEndpointResourceType::MacAddress for interfaceName {}", interface.Name().c_str()); |
| 171 | ProcessNotificationImpl(interface, payload, &GnsEngine::ProcessMacAddressChange); |
| 172 | break; |
| 173 | |
| 174 | case GuestEndpointResourceType::DNS: |
| 175 | GNS_LOG_INFO("GuestEndpointResourceType::DNS for interfaceName {}", interface.Name().c_str()); |
| 176 | ProcessNotificationImpl(interface, payload, &GnsEngine::ProcessDNSChange); |
| 177 | break; |
| 178 | |
| 179 | case GuestEndpointResourceType::Interface: |
| 180 | GNS_LOG_INFO("GuestEndpointResourceType::Interface for interfaceName {}", interface.Name().c_str()); |
| 181 | ProcessNotificationImpl(interface, payload, &GnsEngine::ProcessLinkChange); |
| 182 | break; |
| 183 | |
| 184 | default: |
| 185 | throw RuntimeErrorWithSourceLocation(std::format( |
| 186 | "Unexpected LxGnsMessageNotification for interfaceName {}: {}", interface.Name(), payload["ResourceType"].get<std::string>())); |
| 187 | break; |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | template <typename T> |
| 192 | void GnsEngine::ProcessNotificationImpl( |
| 193 | Interface& interface, const nlohmann::json& payload, void (GnsEngine::*routine)(Interface&, const T&, wsl::shared::hns::ModifyRequestType)) |
| 194 | { |
| 195 | T settings{}; |
| 196 | nlohmann::from_json(payload.at("Settings"), settings); |
| 197 | (this->*routine)(interface, settings, payload["RequestType"].get<wsl::shared::hns::ModifyRequestType>()); |
| 198 | } |
| 199 | |
| 200 | void GnsEngine::ProcessIpAddressChange(Interface& interface, const wsl::shared::hns::IPAddress& payload, wsl::shared::hns::ModifyRequestType action) |
| 201 | { |
| 202 | uint16_t addrFamily = UtilWinAfToLinuxAf(payload.Family); |
| 203 | if (addrFamily != AF_INET && addrFamily != AF_INET6) |
| 204 | { |
| 205 | throw RuntimeErrorWithSourceLocation(std::format("Unexpected family: {}", payload.Family)); |
| 206 | } |
| 207 | |
| 208 | Address address{ |
| 209 | addrFamily, |
| 210 | payload.OnLinkPrefixLength, |
| 211 | wsl::shared::string::WideToMultiByte(payload.Address), |
| 212 | static_cast<IpPrefixOrigin>(payload.PrefixOrigin), |
| 213 | static_cast<IpSuffixOrigin>(payload.SuffixOrigin), |
| 214 | payload.PreferredLifetime}; |
| 215 | |
| 216 | // For addresses plumbed through this path, the corresponding prefix route will be plumbed separately, |
| 217 | // so do not let Linux autogenerate the prefix route. |
| 218 | address.SetIsPrefixRouteAutogenerationDisabled(true); |
| 219 | |
| 220 | const auto addressString = utils::Stringify(address); |
| 221 | |
| 222 | if (action == ModifyRequestType::Remove) |
| 223 | { |
| 224 | GNS_LOG_INFO("Remove address {} on interfaceName {}", addressString.c_str(), interface.Name().c_str()); |
| 225 | manager.ModifyAddress(interface, address, Operation::Remove); |
| 226 | } |
| 227 | else if (action == ModifyRequestType::Add) |
| 228 | { |
| 229 | GNS_LOG_INFO("Add address {} on interfaceName {}", addressString.c_str(), interface.Name().c_str()); |
| 230 | manager.ModifyAddress(interface, address, Operation::Create); |
| 231 | } |
| 232 | else if (action == ModifyRequestType::Update) |
| 233 | { |
| 234 | GNS_LOG_INFO("Update address {} on interfaceName {}", addressString.c_str(), interface.Name().c_str()); |
| 235 | manager.ModifyAddress(interface, address, Operation::Update); |
| 236 | } |
| 237 | else |
| 238 | { |
| 239 | throw RuntimeErrorWithSourceLocation(std::format("Unexpected ip address action: {}", static_cast<int>(action))); |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | void GnsEngine::ProcessRouteChange(Interface& interface, const wsl::shared::hns::Route& route, wsl::shared::hns::ModifyRequestType action) |
| 244 | { |
| 245 | int addrFamily = UtilWinAfToLinuxAf(route.Family); |
| 246 | if (addrFamily != AF_INET && addrFamily != AF_INET6) |
| 247 | { |
| 248 | throw RuntimeErrorWithSourceLocation(std::format("Unexpected family: {}", route.Family)); |
| 249 | } |
| 250 | |
| 251 | if (action == ModifyRequestType::Reset) |
| 252 | { |
| 253 | GNS_LOG_INFO("Reset routes on interfaceName {}", interface.Name().c_str()); |
| 254 | manager.ResetRoutingTable(addrFamily, interface); |
| 255 | return; |
| 256 | } |
| 257 | |
| 258 | bool defaultRoute = (addrFamily == AF_INET && route.DestinationPrefix == LX_INIT_DEFAULT_ROUTE_PREFIX) || |
| 259 | (addrFamily == AF_INET6 && route.DestinationPrefix == LX_INIT_DEFAULT_ROUTE_V6_PREFIX); |
| 260 | std::optional<Address> to; |
| 261 | if (!defaultRoute) |
| 262 | { |
| 263 | to = Address::FromPrefixString(addrFamily, wsl::shared::string::WideToMultiByte(route.DestinationPrefix)); |
| 264 | } |
| 265 | |
| 266 | // Note: for the next hop parameter to the Route constructor, the prefix length can be any valid prefix length - |
| 267 | // it's just used to create an address object. We currently use the SitePrefixLength field for convenience. |
| 268 | const auto nextHopValue = wsl::shared::string::WideToMultiByte(route.NextHop); |
| 269 | auto interfaceRoute = |
| 270 | Route{addrFamily, {{addrFamily, route.SitePrefixLength, nextHopValue}}, interface.Index(), defaultRoute, to, route.Metric}; |
| 271 | |
| 272 | auto routeString = utils::Stringify(interfaceRoute); |
| 273 | |
| 274 | if (action == ModifyRequestType::Add) |
| 275 | { |
| 276 | GNS_LOG_INFO("Add route {} on interfaceName {}", routeString.c_str(), interface.Name().c_str()); |
| 277 | manager.ModifyRoute(interfaceRoute, Operation::Create); |
| 278 | } |
| 279 | else if (action == ModifyRequestType::Remove) |
| 280 | { |
| 281 | GNS_LOG_INFO("Remove route {} on interfaceName {}", routeString.c_str(), interface.Name().c_str()); |
| 282 | manager.ModifyRoute(interfaceRoute, Operation::Remove); |
| 283 | } |
| 284 | else if (action == ModifyRequestType::Update) |
| 285 | { |
| 286 | GNS_LOG_INFO("Update route {} on interfaceName {}", routeString.c_str(), interface.Name().c_str()); |
| 287 | manager.ModifyRoute(interfaceRoute, Operation::Update); |
| 288 | } |
| 289 | else |
| 290 | { |
| 291 | throw RuntimeErrorWithSourceLocation(std::format("Unexpected route action: {}", static_cast<int>(action))); |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | void GnsEngine::ProcessDNSChange(Interface& interface, const wsl::shared::hns::DNS& payload, wsl::shared::hns::ModifyRequestType action) |
| 296 | { |
| 297 | if (action == ModifyRequestType::Remove) |
| 298 | { |
| 299 | GNS_LOG_INFO("Ignoring Remove on interfaceName {}", interface.Name().c_str()); |
| 300 | return; // Will be overwritten when the next 'add' / 'update' comes |
| 301 | } |
| 302 | |
| 303 | if (action != ModifyRequestType::Update && action != ModifyRequestType::Add) |
| 304 | { |
| 305 | throw RuntimeErrorWithSourceLocation(std::format("Unexpected DNS Change action: {}", static_cast<int>(action))); |
| 306 | } |
| 307 | |
| 308 | std::wstringstream content; |
| 309 | if (!payload.Options.empty()) |
| 310 | { |
| 311 | content << payload.Options; // The Options field is used to pass the file header |
| 312 | } |
| 313 | |
| 314 | for (const auto& server : wsl::shared::string::Split(payload.ServerList, L',')) |
| 315 | { |
| 316 | content << L"nameserver " << server << L"\n"; |
| 317 | } |
| 318 | |
| 319 | // Use 'search' for DNS suffixes. |
| 320 | // Per resolv.conf(5): "The domain directive is an obsolete name for the search directive |
| 321 | // that handles one search list entry only." |
| 322 | // See: https://man7.org/linux/man-pages/man5/resolv.conf.5.html |
| 323 | if (!payload.Search.empty()) |
| 324 | { |
| 325 | content << L"search " << wsl::shared::string::Join(wsl::shared::string::Split(payload.Search, L','), L' ') << L"\n"; |
| 326 | } |
| 327 | |
| 328 | GNS_LOG_INFO( |
| 329 | "Setting DNS search to {}: {} on interfaceName {} ", payload.Search.c_str(), content.str().c_str(), interface.Name().c_str()); |
| 330 | |
| 331 | THROW_LAST_ERROR_IF(UtilMkdirPath("/etc", 0755) < 0); |
| 332 | std::wofstream resolvConf; |
| 333 | resolvConf.exceptions(std::ofstream::badbit | std::ofstream::failbit); |
| 334 | resolvConf.open("/etc/resolv.conf", std::ofstream::trunc); |
| 335 | resolvConf << content.str(); |
| 336 | } |
| 337 | |
| 338 | void GnsEngine::ProcessMacAddressChange(Interface& interface, const wsl::shared::hns::MacAddress& address, wsl::shared::hns::ModifyRequestType type) |
| 339 | { |
| 340 | GNS_LOG_INFO( |
| 341 | "Setting to MAC address to {} (will toggle the interface state) on interfaceName {} ", |
| 342 | address.PhysicalAddress.c_str(), |
| 343 | interface.Name().c_str()); |
| 344 | manager.SetAdapterMacAddress(interface, wsl::shared::string::ParseMacAddress(address.PhysicalAddress, '-')); |
| 345 | } |
| 346 | |
| 347 | void GnsEngine::ProcessLinkChange(Interface& interface, const wsl::shared::hns::NetworkInterface& link, wsl::shared::hns::ModifyRequestType type) |
| 348 | { |
| 349 | GNS_LOG_INFO( |
| 350 | "Setting link state to {} on interfaceName {}", |
| 351 | link.Connected ? "InterfaceState::Up" : "InterfaceState::Down", |
| 352 | interface.Name().c_str()); |
| 353 | manager.SetInterfaceState(interface, link.Connected ? NetworkManager::InterfaceState::Up : NetworkManager::InterfaceState::Down); |
| 354 | |
| 355 | if (link.Connected && link.NlMtu != 0) |
| 356 | { |
| 357 | GNS_LOG_INFO("Setting MTU to {} on interfaceName {} ", link.NlMtu, interface.Name().c_str()); |
| 358 | interface.SetMtu(link.NlMtu); |
| 359 | } |
| 360 | |
| 361 | if (link.Connected && link.Metric != 0) |
| 362 | { |
| 363 | GNS_LOG_INFO("Setting Metric to {} on interfaceName {} ", link.Metric, interface.Name().c_str()); |
| 364 | interface.SetMetric(link.Metric); |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | std::tuple<bool, int> GnsEngine::ProcessNextMessage(wsl::shared::Transaction& transaction) |
| 369 | { |
| 370 | int return_value = 0; |
| 371 | |
| 372 | auto payload = notificationRoutine(transaction); |
| 373 | if (!payload.has_value()) |
| 374 | { |
| 375 | GNS_LOG_ERROR("Received empty message, exiting"); |
| 376 | return std::make_tuple(false, -1); |
| 377 | } |
| 378 | |
| 379 | switch (payload->MessageType) |
| 380 | { |
| 381 | case LxGnsMessageNoOp: |
| 382 | { |
| 383 | break; |
| 384 | } |
| 385 | case LxGnsMessageNotification: |
| 386 | { |
| 387 | auto interface = OpenAdapter(payload->AdapterId.value()); |
| 388 | |
| 389 | ProcessNotification(nlohmann::json::parse(payload->Json), interface); |
| 390 | break; |
| 391 | } |
| 392 | case LxGnsMessageInterfaceConfiguration: |
| 393 | { |
| 394 | const auto endpoint = wsl::shared::FromJson<wsl::shared::hns::HNSEndpoint>(payload->Json.c_str()); |
| 395 | const auto endpointString = wsl::shared::string::GuidToString<char>(endpoint.ID); |
| 396 | auto interface = OpenAdapter(endpoint.ID); |
| 397 | |
| 398 | // Give the interface a new name if requested. |
| 399 | if (endpoint.PortFriendlyName.size() > 0) |
| 400 | { |
| 401 | auto assignedName = wsl::shared::string::WideToMultiByte(endpoint.PortFriendlyName); |
| 402 | if (assignedName.compare(interface.Name()) != 0) |
| 403 | { |
| 404 | // Special case for wlanxx adapters: create a virtual wifi interface. |
| 405 | if (assignedName.size() > 3 && assignedName.compare(0, 4, "wlan") == 0) |
| 406 | { |
| 407 | auto backingName = std::string("_") + assignedName; |
| 408 | GNS_LOG_INFO( |
| 409 | "LxGnsMessageInterfaceConfiguration: endpointID ({}) setting interfaceName to {}", |
| 410 | endpointString.c_str(), |
| 411 | backingName.c_str()); |
| 412 | manager.SetAdapterName(interface, backingName); |
| 413 | |
| 414 | GNS_LOG_INFO( |
| 415 | "LxGnsMessageInterfaceConfiguration: endpointID ({}) creating virtual Wi-Fi named {}", |
| 416 | endpointString.c_str(), |
| 417 | assignedName.c_str()); |
| 418 | interface = manager.CreateVirtualWifiAdapter(interface, assignedName); |
| 419 | |
| 420 | auto backingInterface = Interface::Open(backingName); |
| 421 | GNS_LOG_INFO( |
| 422 | "LxGnsMessageInterfaceConfiguration: endpointID ({}) setting interface ({}) state up on the newly " |
| 423 | "created interfaceName {}", |
| 424 | endpointString.c_str(), |
| 425 | backingName.c_str(), |
| 426 | backingInterface.Name().c_str()); |
| 427 | manager.SetInterfaceState(backingInterface, NetworkManager::InterfaceState::Up); |
| 428 | } |
| 429 | else |
| 430 | { |
| 431 | GNS_LOG_INFO( |
| 432 | "LxGnsMessageInterfaceConfiguration: endpointID ({}) setting interfaceName from {} to {}", |
| 433 | endpointString.c_str(), |
| 434 | interface.Name().c_str(), |
| 435 | assignedName.c_str()); |
| 436 | manager.SetAdapterName(interface, assignedName); |
| 437 | interface = Interface::Open(assignedName); |
| 438 | } |
| 439 | } |
| 440 | else |
| 441 | { |
| 442 | GNS_LOG_INFO( |
| 443 | "LxGnsMessageInterfaceConfiguration: no-op - the endpoint ID {} PortFriendlyName ({}) is already matching " |
| 444 | "the interfaceName {}", |
| 445 | endpointString.c_str(), |
| 446 | assignedName.c_str(), |
| 447 | interface.Name().c_str()); |
| 448 | } |
| 449 | } |
| 450 | else |
| 451 | { |
| 452 | GNS_LOG_INFO( |
| 453 | "LxGnsMessageInterfaceConfiguration: no-op - the endpoint ID {} PortFriendlyName is blank", endpointString.c_str()); |
| 454 | } |
| 455 | |
| 456 | // The IP address can be empty if flow steering is enabled (we'll get it from a notification) |
| 457 | if (!endpoint.IPAddress.empty()) |
| 458 | { |
| 459 | manager.SetAdapterConfiguration(interface, endpoint); |
| 460 | } |
| 461 | |
| 462 | manager.SetInterfaceState(interface, NetworkManager::InterfaceState::Up); |
| 463 | break; |
| 464 | } |
| 465 | case LxGnsMessageVmNicCreatedNotification: |
| 466 | { |
| 467 | auto vmNic = wsl::shared::FromJson<wsl::shared::hns::VmNicCreatedNotification>(payload->Json.c_str()); |
| 468 | auto interface = OpenAdapter(vmNic.adapterId); |
| 469 | |
| 470 | GNS_LOG_INFO( |
| 471 | "LxGnsMessageVmNicCreatedNotification: EnableLoopbackRouting on adapterId {}, interfaceName {}", |
| 472 | wsl::shared::string::GuidToString<char>(vmNic.adapterId).c_str(), |
| 473 | interface.Name().c_str()); |
| 474 | manager.EnableLoopbackRouting(interface); |
| 475 | break; |
| 476 | } |
| 477 | case LxGnsMessageCreateDeviceRequest: |
| 478 | { |
| 479 | auto createDeviceRequest = wsl::shared::FromJson<wsl::shared::hns::CreateDeviceRequest>(payload->Json.c_str()); |
| 480 | switch (createDeviceRequest.type) |
| 481 | { |
| 482 | case wsl::shared::hns::DeviceType::Loopback: |
| 483 | { |
| 484 | const GUID emptyGuid{}; |
| 485 | assert(createDeviceRequest.lowerEdgeAdapterId.has_value()); |
| 486 | auto gelnic = OpenAdapter(createDeviceRequest.lowerEdgeAdapterId.value()); |
| 487 | GNS_LOG_INFO( |
| 488 | "LxGnsMessageCreateDeviceRequest [Loopback]: InitializeLoopbackConfiguration deviceName {}, interfaceName {}", |
| 489 | wsl::shared::string::GuidToString<char>(createDeviceRequest.lowerEdgeAdapterId.value_or(emptyGuid)).c_str(), |
| 490 | gelnic.Name().c_str()); |
| 491 | manager.InitializeLoopbackConfiguration(gelnic, createDeviceRequest.flags); |
| 492 | |
| 493 | break; |
| 494 | } |
| 495 | default: |
| 496 | throw RuntimeErrorWithSourceLocation( |
| 497 | std::format("Unexpected Wslcore::Networking::DeviceType : {}", static_cast<int>(createDeviceRequest.type))); |
| 498 | break; |
| 499 | } |
| 500 | break; |
| 501 | } |
| 502 | case LxGnsMessageModifyGuestDeviceSettingRequest: |
| 503 | { |
| 504 | auto modifyRequest = wsl::shared::FromJson<wsl::shared::hns::ModifyGuestEndpointSettingRequest<wsl::shared::hns::NetworkInterface>>( |
| 505 | payload->Json.c_str()); |
| 506 | if (modifyRequest.ResourceType != GuestEndpointResourceType::Interface) |
| 507 | { |
| 508 | GNS_LOG_INFO( |
| 509 | "ModifyGuestEndpointSettingRequest - ignoring request that's not for type Interface (type {}) device " |
| 510 | "{}", |
| 511 | static_cast<uint32_t>(modifyRequest.ResourceType), |
| 512 | modifyRequest.targetDeviceName.value_or(L"<empty>").c_str()); |
| 513 | break; |
| 514 | } |
| 515 | |
| 516 | if (!modifyRequest.targetDeviceName.has_value()) |
| 517 | { |
| 518 | GNS_LOG_INFO("ModifyGuestEndpointSettingRequest targetDeviceName is empty"); |
| 519 | break; |
| 520 | } |
| 521 | |
| 522 | auto interface = OpenInterfaceOrAdapter(modifyRequest.targetDeviceName.value()); |
| 523 | GNS_LOG_INFO( |
| 524 | "ModifyGuestEndpointSettingRequest [Interface]: setting link state for deviceName {} interfaceName {}", |
| 525 | modifyRequest.targetDeviceName->c_str(), |
| 526 | interface.Name().c_str()); |
| 527 | |
| 528 | ProcessLinkChange(interface, modifyRequest.Settings, modifyRequest.RequestType); |
| 529 | break; |
| 530 | } |
| 531 | case LxGnsMessageLoopbackRoutesRequest: |
| 532 | { |
| 533 | auto request = wsl::shared::FromJson<wsl::shared::hns::LoopbackRoutesRequest>(payload->Json.c_str()); |
| 534 | if (request.operation != wsl::shared::hns::OperationType::Create && request.operation != wsl::shared::hns::OperationType::Remove) |
| 535 | { |
| 536 | GNS_LOG_INFO( |
| 537 | "LxGnsMessageLoopbackRoutesRequest - ignoring request that has the wrong operation type {} for interface " |
| 538 | "{}", |
| 539 | static_cast<int>(request.operation), |
| 540 | request.targetDeviceName.c_str()); |
| 541 | break; |
| 542 | } |
| 543 | |
| 544 | int addrFamily = UtilWinAfToLinuxAf(request.family); |
| 545 | if (addrFamily != AF_INET && addrFamily != AF_INET6) |
| 546 | { |
| 547 | throw RuntimeErrorWithSourceLocation(std::format("LxGnsMessageLoopbackRoutesRequest: unexpected family: {}", request.family)); |
| 548 | } |
| 549 | |
| 550 | assert(request.operation == wsl::shared::hns::OperationType::Create || request.operation == wsl::shared::hns::OperationType::Remove); |
| 551 | auto operation = (request.operation == wsl::shared::hns::OperationType::Create) ? Operation::Create : Operation::Remove; |
| 552 | auto interface = OpenInterfaceOrAdapter(request.targetDeviceName); |
| 553 | auto ipAddress = wsl::shared::string::WideToMultiByte(request.ipAddress); |
| 554 | int prefixLen = MAX_PREFIX_LEN(addrFamily); |
| 555 | Address address(addrFamily, prefixLen, ipAddress); |
| 556 | manager.UpdateLoopbackRoute(interface, address, operation); |
| 557 | break; |
| 558 | } |
| 559 | case LxGnsMessageDeviceSettingRequest: |
| 560 | { |
| 561 | auto json = nlohmann::json::parse(payload->Json); |
| 562 | auto interface = OpenInterfaceOrAdapter(json.at("targetDeviceName").get<std::wstring>()); |
| 563 | ProcessNotification(json, interface); |
| 564 | break; |
| 565 | } |
| 566 | case LxGnsMessageInitialIpConfigurationNotification: |
| 567 | { |
| 568 | auto notification = wsl::shared::FromJson<wsl::shared::hns::InitialIpConfigurationNotification>(payload->Json.c_str()); |
| 569 | auto interface = OpenInterfaceOrAdapter(notification.targetDeviceName); |
| 570 | |
| 571 | if (WI_IsFlagClear(notification.flags, wsl::shared::hns::InitialIpConfigurationNotificationFlags::SkipPrimaryRoutingTableUpdate)) |
| 572 | { |
| 573 | auto table = manager.FindRoutingTableIdForInterface(interface); |
| 574 | if (!table.has_value()) |
| 575 | { |
| 576 | throw RuntimeErrorWithSourceLocation(std::format( |
| 577 | "LxGnsMessageInitialIpConfigurationNotification: failed to find routing table with name {}", interface.Name())); |
| 578 | } |
| 579 | |
| 580 | GNS_LOG_INFO( |
| 581 | "LxGnsMessageInitialIpConfigurationNotification: Changing primary routing table to {} with id {}", |
| 582 | interface.Name().c_str(), |
| 583 | table.value()); |
| 584 | manager.ChangePrimaryRoutingTable(table.value()); |
| 585 | } |
| 586 | |
| 587 | GNS_LOG_INFO("LxGnsMessageInitialIpConfigurationNotification: Resetting IPv6 state for interface {}", interface.Name().c_str()); |
| 588 | interface.ResetIpv6State(); |
| 589 | |
| 590 | if (WI_IsFlagClear(notification.flags, wsl::shared::hns::InitialIpConfigurationNotificationFlags::SkipLoopbackRouteReset)) |
| 591 | { |
| 592 | GNS_LOG_INFO("LxGnsMessageInitialIpConfigurationNotification: Wiping loopback routes"); |
| 593 | manager.ResetLoopbackRoutes(); |
| 594 | } |
| 595 | |
| 596 | // EnableIpv4ArpFilter does not need to be called per interface as each interface gets mirrored |
| 597 | // It could be global if we had a single global Init message |
| 598 | // If there are more global init requirements in the future, we should consider a new global message |
| 599 | GNS_LOG_INFO("LxGnsMessageInitialIpConfigurationNotification: Enabling IPv4 arp_filter"); |
| 600 | manager.EnableIpv4ArpFilter(); |
| 601 | break; |
| 602 | } |
| 603 | case LxGnsMessageSetupIpv6: |
| 604 | { |
| 605 | manager.DisableDAD(); |
| 606 | manager.DisableRouterDiscovery(); |
| 607 | manager.DisableIpv6AddressGeneration(); |
| 608 | break; |
| 609 | } |
| 610 | case LxGnsMessageConnectTestRequest: |
| 611 | { |
| 612 | // the payload is where to send the request, not in a JSON format |
| 613 | wsl::shared::conncheck::ConnCheckResult result = manager.SendConnectRequest(payload->Json.c_str()); |
| 614 | // convert the 2 enums into a single integer value |
| 615 | // Ipv4 status will be the lower 16 bits |
| 616 | return_value = static_cast<uint32_t>(result.Ipv4Status); |
| 617 | // Ipv6 status will be the upper 16 bits |
| 618 | return_value |= (static_cast<uint32_t>(result.Ipv6Status) << 16); |
| 619 | GNS_LOG_INFO("LxGnsMessageConnectTestRequest (destination: {}) returning: {:#x}", payload->Json.c_str(), return_value); |
| 620 | break; |
| 621 | } |
| 622 | case LxGnsMessageGlobalNetFilter: |
| 623 | { |
| 624 | // the global network filters exist to 'mark' traffic that is originating from root |
| 625 | // vs. traffic that is originating from another Linux container (namespace) |
| 626 | // it also adds a NAT to the chain for the traffic that is not marked |
| 627 | // see the 'nft add rule' command in the below LxGnsMessageInterfaceNetFilter section |
| 628 | auto runCommand = [](const std::string& command) { THROW_LAST_ERROR_IF(UtilExecCommandLine(command.c_str()) < 0); }; |
| 629 | for (const auto& ip : c_ipStrings) |
| 630 | { |
| 631 | runCommand(std::format("nft add table {} filter", ip)); |
| 632 | runCommand(std::format("nft \"add chain {} filter WSLOUTPUT {{ type filter hook output priority filter; }}\"", ip)); |
| 633 | runCommand(std::format("nft add rule {} filter WSLOUTPUT counter mark set 0x1", ip)); |
| 634 | runCommand(std::format("nft add table {} nat", ip)); |
| 635 | runCommand(std::format("nft \"add chain {} nat WSLPOSTROUTING {{ type nat hook postrouting priority srcnat - 1; }}\"", ip)); |
| 636 | } |
| 637 | |
| 638 | break; |
| 639 | } |
| 640 | case LxGnsMessageInterfaceNetFilter: |
| 641 | { |
| 642 | auto interfaceNetFilterRequest = wsl::shared::FromJson<wsl::shared::hns::InterfaceNetFilterRequest>(payload->Json.c_str()); |
| 643 | auto interface = OpenInterfaceOrAdapter(interfaceNetFilterRequest.targetDeviceName); |
| 644 | |
| 645 | GNS_LOG_INFO( |
| 646 | "LxGnsMessageInterfaceNetFilter for interface {} {{operation={}, startPort={}, endPort={}}}", |
| 647 | interface.Name().c_str(), |
| 648 | static_cast<int>(interfaceNetFilterRequest.operation), |
| 649 | interfaceNetFilterRequest.ephemeralPortRangeStart, |
| 650 | interfaceNetFilterRequest.ephemeralPortRangeEnd); |
| 651 | |
| 652 | switch (interfaceNetFilterRequest.operation) |
| 653 | { |
| 654 | case wsl::shared::hns::OperationType::Create: |
| 655 | { |
| 656 | // Create SNAT rules on the interface. |
| 657 | for (const auto& ip : c_ipStrings) |
| 658 | { |
| 659 | for (const auto& protocol : {"udp", "tcp"}) |
| 660 | { |
| 661 | const auto commandLine = std::format( |
| 662 | "nft add rule {} nat WSLPOSTROUTING oif {} {} sport 1-65535 mark != 0x1 counter masquerade to :{}-{}", |
| 663 | ip, |
| 664 | interface.Name().c_str(), |
| 665 | protocol, |
| 666 | interfaceNetFilterRequest.ephemeralPortRangeStart, |
| 667 | interfaceNetFilterRequest.ephemeralPortRangeEnd); |
| 668 | |
| 669 | GNS_LOG_INFO("LxGnsMessageInterfaceNetFilter (Create): {}", commandLine.c_str()); |
| 670 | THROW_LAST_ERROR_IF(UtilExecCommandLine(commandLine.c_str()) < 0); |
| 671 | } |
| 672 | } |
| 673 | |
| 674 | manager.UpdateMirroredLoopbackRulesForInterface(interface.Name(), Operation::Create); |
| 675 | break; |
| 676 | } |
| 677 | case wsl::shared::hns::OperationType::Remove: |
| 678 | { |
| 679 | // Remove SNAT rules on the interface (one in ipv4 and one in ipv6). |
| 680 | // Rules can only be removed via handle number, so find the handle numbers first. |
| 681 | for (const auto& ip : c_ipStrings) |
| 682 | { |
| 683 | const auto listChainCommand = std::format("nft -a list chain {} nat WSLPOSTROUTING", ip); |
| 684 | std::string listOutputString; |
| 685 | THROW_LAST_ERROR_IF(UtilExecCommandLine(listChainCommand.c_str(), &listOutputString) < 0); |
| 686 | |
| 687 | std::regex pattern("oif\\s+\"" + interface.Name() + "\"\\s+.*handle\\s+(\\d+)"); |
| 688 | std::smatch matches; |
| 689 | std::vector<int> handleNumbers; |
| 690 | auto iter = listOutputString.cbegin(); |
| 691 | while (std::regex_search(iter, listOutputString.cend(), matches, pattern)) |
| 692 | { |
| 693 | handleNumbers.push_back(std::stoi(matches.str(1))); |
| 694 | iter = matches.suffix().first; |
| 695 | } |
| 696 | |
| 697 | for (const auto& handle : handleNumbers) |
| 698 | { |
| 699 | auto commandLine = std::format("nft delete rule {} nat WSLPOSTROUTING handle {}", ip, handle); |
| 700 | GNS_LOG_INFO("LxGnsMessageInterfaceNetFilter (Remove): {}", commandLine.c_str()); |
| 701 | THROW_LAST_ERROR_IF(UtilExecCommandLine(commandLine.c_str()) < 0); |
| 702 | } |
| 703 | } |
| 704 | |
| 705 | manager.UpdateMirroredLoopbackRulesForInterface(interface.Name(), Operation::Remove); |
| 706 | break; |
| 707 | } |
| 708 | default: |
| 709 | throw RuntimeErrorWithSourceLocation(std::format( |
| 710 | "Unexpected Wslcore::Networking::OperationType : {}", static_cast<int>(interfaceNetFilterRequest.operation))); |
| 711 | break; |
| 712 | } |
| 713 | break; |
| 714 | } |
| 715 | |
| 716 | default: |
| 717 | throw RuntimeErrorWithSourceLocation(std::format("Unexpected LX_MESSAGE_TYPE : {}", static_cast<int>(payload->MessageType))); |
| 718 | } |
| 719 | |
| 720 | return std::make_tuple(true, return_value); |
| 721 | } |
| 722 | |
| 723 | void GnsEngine::run() |
| 724 | { |
| 725 | UtilSetThreadName("GnsEngine"); |
| 726 | |
| 727 | while (true) |
| 728 | { |
| 729 | auto transaction = channel.ReceiveTransaction(); |
| 730 | try |
| 731 | { |
| 732 | GNS_LOG_INFO("Processing Next Message"); |
| 733 | auto [should_continue, return_value] = ProcessNextMessage(transaction); |
| 734 | if (!should_continue) |
| 735 | { |
| 736 | break; |
| 737 | } |
| 738 | |
| 739 | GNS_LOG_INFO("Processing Next Message Successful ({:#x})", return_value); |
| 740 | statusRoutine(return_value, "", transaction); |
| 741 | } |
| 742 | catch (const std::exception& e) |
| 743 | { |
| 744 | GNS_LOG_ERROR("Error while processing message: {}", e.what()); |
| 745 | statusRoutine(-1, e.what(), transaction); |
| 746 | } |
| 747 | } |
| 748 | |
| 749 | // ensure our exit path is in the error stream |
| 750 | GNS_LOG_ERROR("exiting"); |
| 751 | } |