| 1 | // Copyright (C) Microsoft Corporation. All rights reserved. |
| 2 | |
| 3 | #include <iostream> |
| 4 | #include <filesystem> |
| 5 | #include <lxwil.h> |
| 6 | #include "lxinitshared.h" |
| 7 | #include "common.h" |
| 8 | #include "NetworkManager.h" |
| 9 | #include "util.h" |
| 10 | #include "address.h" |
| 11 | #include "conncheckshared.h" |
| 12 | #include "RuntimeErrorWithSourceLocation.h" |
| 13 | #include "stringshared.h" |
| 14 | |
| 15 | // Custom table used for storing routes for loopback IPs. |
| 16 | constexpr int c_loopbackRoutingTableId = 127; |
| 17 | // Custom table used for storing routes for local IPs. A separate table is used for local IPs so that |
| 18 | // when the IP addresses are changing, all routes from the table can be deleted before adding routes for the new set of IPs. |
| 19 | // The routes for the loopback IPs will always be the same, thus keeping them in a separate table |
| 20 | constexpr int c_localRoutingTableId = 128; |
| 21 | |
| 22 | // See comments in AddMirroredLoopbackRoutingRules for explanation about those priorities. |
| 23 | constexpr int c_WindowsToLinuxRulePriority = 0; |
| 24 | constexpr int c_LinuxToWindowsRulePriority = 1; |
| 25 | constexpr int c_LocalRulePriority = 2; |
| 26 | |
| 27 | // Creates routing tables per interface, identified by the interface index plus an offset. |
| 28 | constexpr int c_routeTableOffsetFromIndex = 1000; |
| 29 | |
| 30 | // All loopback/local packets will be sent out of the guest via those gateways. There |
| 31 | // will be static ARP entries matching those gateways to the MAC address below, such that |
| 32 | // every packet will have that as destination MAC. |
| 33 | // |
| 34 | // Note: Eventually a mechanism will be needed to replace the gateway addresses in case they |
| 35 | // conflict with other addresses in the network. |
| 36 | const Address c_ipv4LoopbackGateway = {AF_INET, Ipv4MaxPrefixLen, LX_INIT_IPV4_LOOPBACK_GATEWAY_ADDRESS}; |
| 37 | const Address c_ipv6LoopbackGateway = {AF_INET6, Ipv6MaxPrefixLen, LX_INIT_IPV6_LOOPBACK_GATEWAY_ADDRESS}; |
| 38 | |
| 39 | // v4 and v6 loopback address range used in Mirrored mode: 127.0.0.1/32 and ::1/128. |
| 40 | // |
| 41 | // Note: Although the v4 loopback address range is 127.0.0.0/8, only traffic to 127.0.0.1 can be used to communicate host<->guest |
| 42 | // in Mirrored mode. Traffic to other v4 loopback addresses will stay in the guest. This can be changed if other loopback |
| 43 | // addresses are needed by host<->guest loopback scenarios. |
| 44 | const Address c_loopbackV4AddressRange = {AF_INET, Ipv4MaxPrefixLen, "127.0.0.1"}; |
| 45 | const Address c_loopbackV6AddressRange = {AF_INET6, Ipv6MaxPrefixLen, "::1"}; |
| 46 | |
| 47 | // 00:11:22:33:44:55 represents the MAC address that all loopback/local packets will have as destination |
| 48 | // MAC when they are sent out of the guest. This will help Windows to identify which |
| 49 | // packets are loopback/local. |
| 50 | const MacAddress c_gatewayMacAddress = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; |
| 51 | |
| 52 | constexpr const char* c_acceptLocalSetting = "accept_local"; |
| 53 | constexpr const char* c_routeLocalnetSetting = "route_localnet"; |
| 54 | constexpr char c_disableSetting[] = "0\n"; |
| 55 | constexpr char c_enableSetting[] = "1\n"; |
| 56 | |
| 57 | NetworkManager::NetworkManager(RoutingTable& routingTable) : |
| 58 | routingTable(routingTable), loopbackRoutingTable(c_loopbackRoutingTableId), localRoutingTable(c_localRoutingTableId) |
| 59 | { |
| 60 | } |
| 61 | |
| 62 | std::optional<int> NetworkManager::FindRoutingTableIdForInterface(const Interface& interface) const |
| 63 | { |
| 64 | return c_routeTableOffsetFromIndex + interface.Index(); |
| 65 | } |
| 66 | |
| 67 | void NetworkManager::ChangePrimaryRoutingTable(int newTableId) |
| 68 | { |
| 69 | routingTable.ChangeTableId(newTableId); |
| 70 | } |
| 71 | |
| 72 | std::vector<Route> NetworkManager::ListRoutes(int family) const |
| 73 | { |
| 74 | return routingTable.ListRoutes(family); |
| 75 | } |
| 76 | |
| 77 | Interface NetworkManager::CreateVirtualWifiAdapter(Interface& baseAdapter, const std::string& wifiName) |
| 78 | { |
| 79 | GNS_LOG_INFO("Creating virtual wifi adapter with name {}", wifiName.c_str()); |
| 80 | baseAdapter.CreateVirtualWifiAdapter(wifiName); |
| 81 | auto virtualWifi = Interface::Open(wifiName); |
| 82 | |
| 83 | GNS_LOG_INFO("Enabling Ipv4 loopback routing on virtual wifi adapter with name {}", wifiName.c_str()); |
| 84 | EnableLoopbackRouting(virtualWifi); |
| 85 | return virtualWifi; |
| 86 | } |
| 87 | |
| 88 | Interface NetworkManager::CreateProxyWifiAdapter(Interface& baseAdapter, const std::string& wifiName) |
| 89 | { |
| 90 | baseAdapter.CreateProxyWifiAdapter(wifiName); |
| 91 | auto proxyWifi = Interface::Open(wifiName); |
| 92 | |
| 93 | GNS_LOG_INFO("Enabling Ipv4 loopback routing on proxy wifi adapter with name {}", wifiName.c_str()); |
| 94 | EnableLoopbackRouting(proxyWifi); |
| 95 | return proxyWifi; |
| 96 | } |
| 97 | |
| 98 | // SUPPORTS IPV4 ONLY, and only supports 1 IP address per adapter. |
| 99 | // Not used for mirroring. |
| 100 | void NetworkManager::SetAdapterConfiguration(Interface& interface, const wsl::shared::hns::HNSEndpoint& configuration) |
| 101 | { |
| 102 | InterfaceConfiguration config; |
| 103 | config.Addresses.emplace_back(Address{AF_INET, configuration.PrefixLength, wsl::shared::string::WideToMultiByte(configuration.IPAddress)}); |
| 104 | |
| 105 | config.LocalAddresses = config.Addresses; |
| 106 | config.BroadcastAddress = utils::ComputeBroadcastAddress(config.Addresses[0]); |
| 107 | |
| 108 | std::stringstream addressInfo; |
| 109 | addressInfo << config.Addresses[0]; |
| 110 | GNS_LOG_INFO( |
| 111 | "Setting the IPv4 address on endpointID ({}) to {} on interfaceName {}", |
| 112 | wsl::shared::string::GuidToString<char>(configuration.ID).c_str(), |
| 113 | addressInfo.str().c_str(), |
| 114 | interface.Name().c_str()); |
| 115 | interface.SetIpv4Configuration(config); |
| 116 | } |
| 117 | |
| 118 | void NetworkManager::SetInterfaceState(Interface& adapter, InterfaceState state) |
| 119 | { |
| 120 | GNS_LOG_INFO("Setting interface state to {} on interfaceName {}", state == InterfaceState::Up ? "Up" : "Down", adapter.Name().c_str()); |
| 121 | if (state == InterfaceState::Up) |
| 122 | { |
| 123 | adapter.SetUp(); |
| 124 | } |
| 125 | else |
| 126 | { |
| 127 | adapter.SetDown(); |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | void NetworkManager::SetAdapterName(Interface& adapter, const std::string& name) |
| 132 | { |
| 133 | adapter.SetName(name); |
| 134 | } |
| 135 | |
| 136 | void NetworkManager::SetAdapterNamespace(Interface& adapter, int namespaceFd) |
| 137 | { |
| 138 | adapter.SetNamespace(namespaceFd); |
| 139 | } |
| 140 | |
| 141 | void NetworkManager::SetWiphyNamespace(Interface& adapter, int namespaceFd) |
| 142 | { |
| 143 | adapter.SetWiphyNamespace(namespaceFd); |
| 144 | } |
| 145 | |
| 146 | void NetworkManager::ModifyRoute(const Route& route, Operation operation) |
| 147 | { |
| 148 | routingTable.ModifyRoute(route, operation); |
| 149 | } |
| 150 | |
| 151 | void NetworkManager::ResetRoutingTable(int addressFamily, const Interface& interface) |
| 152 | { |
| 153 | auto routes = routingTable.ListRoutes(addressFamily); |
| 154 | auto pred = [&](const Route& route) { |
| 155 | // Routes without gateways are link level routes (scope link) |
| 156 | return !route.via.has_value() || route.dev != interface.Index(); |
| 157 | }; |
| 158 | |
| 159 | std::erase_if(routes, pred); |
| 160 | |
| 161 | for (const auto& e : routes) |
| 162 | { |
| 163 | const auto routeString = utils::Stringify(e); |
| 164 | try |
| 165 | { |
| 166 | GNS_LOG_INFO("Removing route {} from interfaceName {}", routeString, interface.Name()); |
| 167 | routingTable.ModifyRoute(e, Operation::Remove); |
| 168 | } |
| 169 | catch (const std::exception& ex) |
| 170 | { |
| 171 | throw RuntimeErrorWithSourceLocation(std::format("Failed to remove route '{}', {}", routeString, ex.what())); |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | void NetworkManager::ModifyAddress(Interface& adapter, const Address& address, Operation operation) |
| 177 | { |
| 178 | std::vector<Route> routes; |
| 179 | |
| 180 | // If the ip address is changing, the routing table needs to be saved & restored |
| 181 | // because netlink doesn't allow ip addresses to be changed, but only deleted and added, |
| 182 | // which causes the routing rules attached to the interface to be dropped |
| 183 | if (operation == Operation::Update) |
| 184 | { |
| 185 | routes = routingTable.ListRoutes(address.Family()); |
| 186 | } |
| 187 | |
| 188 | adapter.ModifyIpAddress(address, operation); |
| 189 | |
| 190 | // Restore the routes for this interface. |
| 191 | // Note: If a route fails to be restored, it's probably because the new address's subnet is different, |
| 192 | // and so the route would have been unusable with the new address anyway |
| 193 | for (const auto& savedRoute : routes) |
| 194 | { |
| 195 | if (savedRoute.dev == adapter.Index() && savedRoute.via.has_value()) |
| 196 | { |
| 197 | const auto savedRouteString = utils::Stringify(savedRoute); |
| 198 | |
| 199 | try |
| 200 | { |
| 201 | GNS_LOG_INFO( |
| 202 | "Restoring route {} after address change, on interfaceName {}", savedRouteString.c_str(), adapter.Name().c_str()); |
| 203 | routingTable.ModifyRoute(savedRoute, Operation::Create); |
| 204 | } |
| 205 | catch (const std::exception& ex) |
| 206 | { |
| 207 | GNS_LOG_ERROR( |
| 208 | "Failed to restore route {} after address change, on interfaceName {}, caught exception " |
| 209 | "{}", |
| 210 | savedRouteString.c_str(), |
| 211 | adapter.Name().c_str(), |
| 212 | ex.what()); |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | void NetworkManager::SetAdapterMacAddress(Interface& interface, const MacAddress& address) |
| 219 | { |
| 220 | SetInterfaceState(interface, InterfaceState::Down); |
| 221 | interface.SetMacAddress(address); |
| 222 | SetInterfaceState(interface, InterfaceState::Up); |
| 223 | } |
| 224 | |
| 225 | void NetworkManager::DisassociateAdapterFromBond(const std::string& bondInterfaceName, Interface& interface) |
| 226 | { |
| 227 | auto bondInterface = Interface::Open(bondInterfaceName); |
| 228 | GNS_LOG_INFO( |
| 229 | "Trying to disassociate from bond - bondDeviceName {}, interfaceName {}", bondInterfaceName.c_str(), interface.Name().c_str()); |
| 230 | bondInterface.RemoveFromBond(interface); |
| 231 | GNS_LOG_INFO( |
| 232 | "Successfully disassociated from bond - bondDeviceName {}, interfaceName {}", bondInterfaceName.c_str(), interface.Name().c_str()); |
| 233 | } |
| 234 | |
| 235 | void NetworkManager::AssociateAdapterWithBond(const std::string& bondInterfaceName, Interface& interface) |
| 236 | { |
| 237 | auto bondInterface = Interface::Open(bondInterfaceName); |
| 238 | // must set the interface down before associating it to bond |
| 239 | SetInterfaceState(interface, InterfaceState::Down); |
| 240 | bondInterface.AddToBond(interface); |
| 241 | GNS_LOG_INFO( |
| 242 | "Successfully associated to bond - bondDeviceName {}, interfaceName {}", bondInterfaceName.c_str(), interface.Name().c_str()); |
| 243 | SetInterfaceState(interface, InterfaceState::Up); |
| 244 | } |
| 245 | |
| 246 | void NetworkManager::ActivateAdapterWithBond(const std::string& bondInterfaceName, const Interface& interface) |
| 247 | { |
| 248 | auto bondInterface = Interface::Open(bondInterfaceName); |
| 249 | bondInterface.SetActiveChild(interface); |
| 250 | } |
| 251 | |
| 252 | Interface NetworkManager::CreateBondAdapter(const std::string& name) |
| 253 | { |
| 254 | Interface::CreateBondAdapter(name); |
| 255 | auto bondInterface = Interface::Open(name); |
| 256 | |
| 257 | // Enable routing of IPv4 loopback on the bond interface. |
| 258 | GNS_LOG_INFO("Enabling IPv4 loopback routing on bond adapter with name {}", name.c_str()); |
| 259 | EnableLoopbackRouting(bondInterface); |
| 260 | return bondInterface; |
| 261 | } |
| 262 | |
| 263 | /* |
| 264 | Enable the accept_local and route_localnet settings required to send/receive loopback and local |
| 265 | packets on an interface. |
| 266 | |
| 267 | Note: The function supports only IPv4 settings at the moment. There are no equivalent IPv6 settings |
| 268 | for accept_local and route_localnet. |
| 269 | */ |
| 270 | void NetworkManager::EnableLoopbackRouting(Interface& interface) |
| 271 | { |
| 272 | GNS_LOG_INFO("Enabling sysctl accept_local setting on adapter with name {}", interface.Name().c_str()); |
| 273 | interface.EnableNetworkSetting(c_acceptLocalSetting, AF_INET); |
| 274 | |
| 275 | GNS_LOG_INFO("Enabling sysctl route_localnet setting on adapter with name {}", interface.Name().c_str()); |
| 276 | interface.EnableNetworkSetting(c_routeLocalnetSetting, AF_INET); |
| 277 | } |
| 278 | |
| 279 | /* |
| 280 | Note: GELNIC stands for Guest-Exclusive Loopback NIC. It represents the mirrored interface of the host |
| 281 | loopback interface. Every packet that arrives in the guest having a loopback destination address will |
| 282 | arrive on the GELNIC. |
| 283 | */ |
| 284 | void NetworkManager::InitializeLoopbackConfiguration(Interface& gelnic, wsl::shared::hns::CreateDeviceFlags flags) |
| 285 | { |
| 286 | if (WI_IsFlagSet(flags, wsl::shared::hns::CreateDeviceFlags::DisableDAD)) |
| 287 | { |
| 288 | gelnic.DisableNetworkSetting("accept_dad", AF_INET6); |
| 289 | gelnic.DisableNetworkSetting("dad_transmits", AF_INET6); |
| 290 | |
| 291 | // Toggle ipv6 to reset our temporary address. |
| 292 | gelnic.EnableNetworkSetting("disable_ipv6", AF_INET6); |
| 293 | gelnic.DisableNetworkSetting("disable_ipv6", AF_INET6); |
| 294 | } |
| 295 | |
| 296 | // Enable routing of IPv4 loopback on the GELNIC. |
| 297 | GNS_LOG_INFO("Enabling IPv4 loopback routing on GELNIC adapter {}", gelnic.Name().c_str()); |
| 298 | EnableLoopbackRouting(gelnic); |
| 299 | |
| 300 | // Disable IPv4 reverse path filtering on the GELNIC. |
| 301 | // The effective rp_filter setting for interface "name" is the more restrictive between "name" and "all", so both must be set. |
| 302 | GNS_LOG_INFO("Disabling sysctl rp_filter setting on loopback adapter"); |
| 303 | gelnic.DisableNetworkSetting("rp_filter", AF_INET); |
| 304 | ModifyNetSetting(AF_INET, "rp_filter", "all", c_disableSetting, strlen(c_disableSetting)); |
| 305 | |
| 306 | InitializeLoopbackConfigurationImpl(gelnic, AF_INET); |
| 307 | // InitializeLoopbackConfigurationImpl(gelnic, AF_INET6); |
| 308 | } |
| 309 | |
| 310 | /* |
| 311 | In mirrored networking mode, Linux ip rules (policy based routing) are configured such that |
| 312 | loopback traffic or local traffic (local = traffic with destination an IP assigned to Linux/Windows) |
| 313 | can flow between Windows and Linux. Note: This applies only to TCP and UDP traffic. |
| 314 | |
| 315 | Below are outputs of "ip rule show" in both NAT and mirrored mode, along with explanations about how |
| 316 | the rules work. |
| 317 | |
| 318 | The leftmost part of each rule represents the priority of the rule (priority 0 is the highest priority) |
| 319 | |
| 320 | The "lookup" keyword is followed by the name or id of a routing table |
| 321 | The local table is used by Linux to know when to deliver a packet locally (to a Linux process). This table is used |
| 322 | for both traffic with destination 127.0.0.1 and traffic with destination an IP assigned to Linux. |
| 323 | Table 127 contains routes used to send traffic with destination 127.0.0.1 out of Linux, to be processed by Windows. |
| 324 | Traffic will be sent out of Linux via the "GELNIC" interface (which will be called loopback0). |
| 325 | Table 128 contains routes used to send traffic with local destination out of Linux, to be processed by Windows. |
| 326 | Traffic will be sent out of Linux via the mirrored interface that has the destination IP assigned to it. |
| 327 | Tables main and default contain routes that are not related to loopback traffic |
| 328 | |
| 329 | Priority 0 rules will deliver to Linux loopback/local traffic coming from Windows (this can be traffic |
| 330 | with origin in Windows or traffic with origin in Linux that was sent to Windows and Windows sent it back to Linux). |
| 331 | "iif" represents input interface (the interface on which Linux received the traffic). |
| 332 | "loopback0" is the interface used to send 127.0.0.1 traffic between Linux and Windows. "eth0" refers to an |
| 333 | interface that was mirrored in Linux. Each time an interface is mirrored, a rule like this needs to be added |
| 334 | for that interface. And each time the interface is deleted, the rule needs to be deleted. |
| 335 | |
| 336 | Priority 1 rules are used to send traffic that originates in Linux out of Linux to Windows, so that Windows can decide |
| 337 | if that traffic must be sent to Windows or back to Linux. Those rules apply to traffic originating in the Linux |
| 338 | root network namespace, but also to traffic that root network namespace receives from other network namespaces, such |
| 339 | as Docker containers. |
| 340 | |
| 341 | The priority 2 rule is needed for loopback/local traffic that is not TCP or UDP, such as ICMP. This traffic will |
| 342 | stay inside Linux, it cannot be sent between Linux and Windows. The rule is also needed for receiving inbound traffic |
| 343 | from an external machine. |
| 344 | |
| 345 | NAT mode ip rule show: |
| 346 | 0: from all lookup local |
| 347 | 32766: from all lookup main |
| 348 | 32767: from all lookup default |
| 349 | |
| 350 | mirrored mode ip rule show: |
| 351 | 0: from all iif loopback0 ipproto tcp lookup local |
| 352 | 0: from all iif loopback0 ipproto udp lookup local |
| 353 | 0: from all iif eth0 ipproto tcp lookup local |
| 354 | 0: from all iif eth0 ipproto udp lookup local |
| 355 | 1: from all ipproto tcp lookup 127 |
| 356 | 1: from all ipproto udp lookup 127 |
| 357 | 1: from all ipproto tcp lookup 128 |
| 358 | 1: from all ipproto udp lookup 128 |
| 359 | 2: from all lookup local |
| 360 | 32766: from all lookup main |
| 361 | 32767: from all lookup default |
| 362 | */ |
| 363 | void NetworkManager::AddMirroredLoopbackRoutingRules(Interface& gelnic, int addressFamily) |
| 364 | { |
| 365 | GNS_LOG_INFO("gelnic name {}, addressFamily {}", gelnic.Name().c_str(), addressFamily); |
| 366 | |
| 367 | // Delete rule with priority 0 for local table (from all prio 0 lookup local). |
| 368 | Rule rule = Rule(addressFamily, RT_TABLE_LOCAL, 0); |
| 369 | ruleManager.ModifyRoutingTablePriority(rule, Operation::Remove); |
| 370 | |
| 371 | // Adding priority 0 rules for the GELNIC interface |
| 372 | // Similar priority 0 rules will also be added or deleted when an interface is mirrored in Linux, or deleted |
| 373 | UpdateMirroredLoopbackRulesForInterface(gelnic.Name(), Operation::Create); |
| 374 | |
| 375 | auto AddPriority1Rule = [&](const Protocol protocol, const int routingTableId) { |
| 376 | Rule rule = Rule(addressFamily, routingTableId, c_LinuxToWindowsRulePriority, protocol); |
| 377 | ruleManager.ModifyRoutingTablePriorityWithProtocol(rule, Operation::Create); |
| 378 | }; |
| 379 | |
| 380 | // Adding priority 1 rules |
| 381 | AddPriority1Rule(Protocol::Tcp, c_loopbackRoutingTableId); |
| 382 | AddPriority1Rule(Protocol::Udp, c_loopbackRoutingTableId); |
| 383 | AddPriority1Rule(Protocol::Tcp, c_localRoutingTableId); |
| 384 | AddPriority1Rule(Protocol::Udp, c_localRoutingTableId); |
| 385 | |
| 386 | // Add a rule referencing the local table, with priority 2 |
| 387 | rule = Rule(addressFamily, RT_TABLE_LOCAL, c_LocalRulePriority); |
| 388 | ruleManager.ModifyRoutingTablePriority(rule, Operation::Create); |
| 389 | } |
| 390 | |
| 391 | void NetworkManager::UpdateMirroredLoopbackRulesForInterface(const std::string& interfaceName, Operation operation) |
| 392 | { |
| 393 | assert(operation == Operation::Create || operation == Operation::Remove); |
| 394 | |
| 395 | // Add or remove priority 0 rules needed by mirrored loopback traffic. See the comments in AddMirroredLoopbackRoutingRules for |
| 396 | // more details. Currently only IPv4 guest<->host loopback is supported in mirrored mode - adding only IPv4 rules. |
| 397 | GNS_LOG_INFO( |
| 398 | "{} priority 0 rule for interfaceName {} for TCP", operation == Operation::Create ? "Add" : "Remove", interfaceName.c_str()); |
| 399 | Rule rule = Rule(AF_INET, RT_TABLE_LOCAL, c_WindowsToLinuxRulePriority, Protocol::Tcp); |
| 400 | rule.iif = interfaceName; |
| 401 | ruleManager.ModifyLoopbackRule(rule, operation); |
| 402 | |
| 403 | GNS_LOG_INFO( |
| 404 | "{} priority 0 rule for interfaceName {} for UDP", operation == Operation::Create ? "Add" : "Remove", interfaceName.c_str()); |
| 405 | rule = Rule(AF_INET, RT_TABLE_LOCAL, c_WindowsToLinuxRulePriority, Protocol::Udp); |
| 406 | rule.iif = interfaceName; |
| 407 | ruleManager.ModifyLoopbackRule(rule, operation); |
| 408 | } |
| 409 | |
| 410 | /* |
| 411 | Adds the policy rules required for loopback. Also adds routes for the loopback address range |
| 412 | 127.0.0.1/32 or ::1/128. |
| 413 | */ |
| 414 | void NetworkManager::InitializeLoopbackConfigurationImpl(Interface& gelnic, int addressFamily) |
| 415 | { |
| 416 | // Set to GELNIC to status up before adding the configurations |
| 417 | gelnic.SetUp(); |
| 418 | |
| 419 | AddMirroredLoopbackRoutingRules(gelnic, addressFamily); |
| 420 | |
| 421 | auto gateway = addressFamily == AF_INET ? c_ipv4LoopbackGateway : c_ipv6LoopbackGateway; |
| 422 | auto addressRange = addressFamily == AF_INET ? c_loopbackV4AddressRange : c_loopbackV6AddressRange; |
| 423 | |
| 424 | // Add a static ARP entry for the loopback gateway. The purpose of the static entries is |
| 425 | // to guarantee that each loopback packet that leaves the guest has the same destination MAC. |
| 426 | GNS_LOG_INFO("Adding static ARP entry for the loopback gateway {}", gateway.Addr().c_str()); |
| 427 | Neighbor neighbor = Neighbor(gateway, c_gatewayMacAddress, gelnic.Index()); |
| 428 | neighborManager.ModifyNeighborEntry(neighbor, Operation::Create); |
| 429 | |
| 430 | // Add routes for 127.0.0.1/32 or ::1/128 |
| 431 | Route route = Route(addressFamily, gateway, gelnic.Index(), false, addressRange, 0); |
| 432 | route.isLoopbackRoute = true; |
| 433 | |
| 434 | const auto routeString = utils::Stringify(route); |
| 435 | GNS_LOG_INFO("Add route {} on GELNIC adapter {}", routeString.c_str(), gelnic.Name().c_str()); |
| 436 | loopbackRoutingTable.ModifyRoute(route, Operation::Create); |
| 437 | } |
| 438 | |
| 439 | /* |
| 440 | Add or remove loopback routes for the set of IP addresses that were added/deleted on an interface. All routes are via the |
| 441 | same gateway address. The function can be used for both IPv4 and IPv6 addresses. |
| 442 | */ |
| 443 | void NetworkManager::UpdateLoopbackRoute(Interface& interface, const Address& address, Operation operation) |
| 444 | { |
| 445 | assert(operation == Operation::Create || operation == Operation::Remove); |
| 446 | |
| 447 | // For the moment don't process IPv6 addresses, since inbound IPv6 loopback is not supported yet (dropped by |
| 448 | // default by the Linux stack). Once that is addressed, this check will be removed. |
| 449 | if (address.Family() == AF_INET6) |
| 450 | { |
| 451 | GNS_LOG_INFO("Ignoring IPv6 address {}", utils::Stringify(address).c_str()); |
| 452 | return; |
| 453 | } |
| 454 | |
| 455 | auto gateway = address.Family() == AF_INET ? c_ipv4LoopbackGateway : c_ipv6LoopbackGateway; |
| 456 | |
| 457 | if (operation == Operation::Create) |
| 458 | { |
| 459 | // When adding routes, always add the static neighbor entry for the loopback gateway. The purpose of the static entries is |
| 460 | // to guarantee that each loopback packet that leaves the guest has the same destination MAC. |
| 461 | // |
| 462 | // Note: The entries are added each time we add routes in order to avoid keeping track of whether they are added or not |
| 463 | // (as the entries will be lost when an interface changes state to down). |
| 464 | GNS_LOG_INFO("Adding static neighbor entry for the loopback gateway {}", gateway.Addr().c_str()); |
| 465 | Neighbor neighbor = Neighbor(gateway, c_gatewayMacAddress, interface.Index()); |
| 466 | neighborManager.ModifyNeighborEntry(neighbor, Operation::Create); |
| 467 | } |
| 468 | |
| 469 | Route route = Route(address.Family(), gateway, interface.Index(), false, address, 0); |
| 470 | route.isLoopbackRoute = true; |
| 471 | |
| 472 | const auto routeString = utils::Stringify(route); |
| 473 | GNS_LOG_INFO( |
| 474 | "{} loopback route {} on interfaceName {}", |
| 475 | operation == Operation::Create ? "Add" : "Remove", |
| 476 | routeString.c_str(), |
| 477 | interface.Name().c_str()); |
| 478 | localRoutingTable.ModifyRoute(route, operation); |
| 479 | } |
| 480 | |
| 481 | void NetworkManager::ResetLoopbackRoutes() |
| 482 | { |
| 483 | localRoutingTable.RemoveAll(AF_UNSPEC); |
| 484 | } |
| 485 | |
| 486 | void NetworkManager::CreateTunAdapter(const std::string& name) |
| 487 | { |
| 488 | Interface::CreateTunAdapter(name); |
| 489 | |
| 490 | // Enable routing of IPv4 loopback on the tunnel interface. |
| 491 | GNS_LOG_INFO("Enabling IPv4 loopback routing on tunnel adapter with name {}", name.c_str()); |
| 492 | Interface tunInterface = {-1, name}; |
| 493 | EnableLoopbackRouting(tunInterface); |
| 494 | } |
| 495 | |
| 496 | void NetworkManager::ModifyNetSetting(int addressFamily, const char* settingName, const char* scope, const char* settingValue, size_t settingValueLen) |
| 497 | { |
| 498 | const std::filesystem::path settingFilePath = |
| 499 | std::format("/proc/sys/net/{}/conf/{}/{}", ((addressFamily == AF_INET) ? "ipv4" : "ipv6"), scope, settingName); |
| 500 | wil::unique_fd fd(Syscall(open, settingFilePath.c_str(), (O_WRONLY | O_CLOEXEC))); |
| 501 | Syscall(write, fd.get(), settingValue, settingValueLen); |
| 502 | } |
| 503 | |
| 504 | void NetworkManager::DisableRouterDiscovery() |
| 505 | { |
| 506 | ModifyNetSetting(AF_INET6, "accept_ra", "all", c_disableSetting, strlen(c_disableSetting)); |
| 507 | ModifyNetSetting(AF_INET6, "accept_ra", "default", c_disableSetting, strlen(c_disableSetting)); |
| 508 | } |
| 509 | |
| 510 | void NetworkManager::DisableDAD() |
| 511 | { |
| 512 | // DAD is not enabled for IPv4 by default on Linux-based systems, so only disable for IPv6. |
| 513 | ModifyNetSetting(AF_INET6, "dad_transmits", "all", c_disableSetting, strlen(c_disableSetting)); |
| 514 | ModifyNetSetting(AF_INET6, "dad_transmits", "default", c_disableSetting, strlen(c_disableSetting)); |
| 515 | } |
| 516 | |
| 517 | void NetworkManager::DisableIpv6AddressGeneration() |
| 518 | { |
| 519 | // Disable autoconfiguration. |
| 520 | ModifyNetSetting(AF_INET6, "autoconf", "all", c_disableSetting, strlen(c_disableSetting)); |
| 521 | ModifyNetSetting(AF_INET6, "autoconf", "default", c_disableSetting, strlen(c_disableSetting)); |
| 522 | |
| 523 | // Disable link local address generation. |
| 524 | constexpr char c_genModeNone[] = "1\n"; |
| 525 | ModifyNetSetting(AF_INET6, "addr_gen_mode", "all", c_genModeNone, strlen(c_genModeNone)); |
| 526 | ModifyNetSetting(AF_INET6, "addr_gen_mode", "default", c_genModeNone, strlen(c_genModeNone)); |
| 527 | |
| 528 | // Disable privacy extensions, i.e. temporary address generation. |
| 529 | ModifyNetSetting(AF_INET6, "use_tempaddr", "all", c_disableSetting, strlen(c_disableSetting)); |
| 530 | ModifyNetSetting(AF_INET6, "use_tempaddr", "default", c_disableSetting, strlen(c_disableSetting)); |
| 531 | } |
| 532 | |
| 533 | void NetworkManager::EnableIpv4ArpFilter() |
| 534 | { |
| 535 | // sets /proc/sys/net/ipv4/conf/all/arp_filter to a value of 1 |
| 536 | // this is to stop Linux from attempting to ARP a configured IP address across all connected interfaces |
| 537 | // setting this to 1 instructs Linux to only ARP for that address over the interface that the address was assigned |
| 538 | // This setting is required to avoid breaking mirroring where multiple interfaces are mirrored on the same network (they have |
| 539 | // addresses on the same prefix) which can cause the Host to interpret an ARP from an interface without the address to be a |
| 540 | // duplicate which causes the host fail DAD, and DHCP immediately requests a new address (this will just continue in a loop) |
| 541 | ModifyNetSetting(AF_INET, "arp_filter", "all", c_enableSetting, strlen(c_enableSetting)); |
| 542 | ModifyNetSetting(AF_INET, "arp_filter", "default", c_enableSetting, strlen(c_enableSetting)); |
| 543 | } |
| 544 | |
| 545 | wsl::shared::conncheck::ConnCheckResult NetworkManager::SendConnectRequest(const char* remoteAddress) |
| 546 | { |
| 547 | return wsl::shared::conncheck::CheckConnection(remoteAddress, nullptr, "80"); |
| 548 | } |