master
cpp 751 lines 30.9 KB
Raw
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 }