master
h 324 lines 11.2 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCVirtualMachine.h
8
9 Abstract:
10
11 WSLCVirtualMachine manages the client-side lifecycle of a WSLC virtual machine.
12
13 The VM is created via IWSLCVirtualMachine (running in the SYSTEM service), and this class
14 connects to the existing VM for unprivileged operations. Privileged operations
15 like AttachDisk and AddShare are delegated back to IWSLCVirtualMachine.
16
17 --*/
18 #pragma once
19 #include "wslc.h"
20 #include "hcs.hpp"
21 #include "WSLCProcess.h"
22 #include "WSLCContainerMetadata.h"
23 #include <thread>
24 #include <filesystem>
25 #include <optional>
26 #include <set>
27
28 namespace wsl::windows::service::wslc {
29
30 enum WSLCMountFlags
31 {
32 WSLCMountFlagsNone = 0,
33 WSLCMountFlagsReadOnly = 1,
34 WSLCMountFlagsChroot = 2,
35 WSLCMountFlagsWriteableOverlayFs = 4,
36 };
37
38 enum WSLCFdType
39 {
40 WSLCFdTypeDefault = 0,
41 WSLCFdTypeTty = 1,
42 WSLCFdTypeTtyControl = 2,
43 };
44
45 struct WSLCProcessFd
46 {
47 LONG Fd{};
48 WSLCFdType Type{};
49 };
50
51 class WSLCVirtualMachine;
52
53 // Owns the set of in-use VM-side port numbers for a single VM instance. Held by shared_ptr from the
54 // VM (sole owner) and referenced weakly by each VmPortAllocation, so a VM teardown drops every
55 // reservation and any surviving allocation self-neuters instead of dangling into a freed VM.
56 struct VmPortReservations
57 {
58 std::mutex Mutex;
59 std::set<uint16_t> Ports;
60 };
61
62 struct VmPortAllocation
63 {
64 NON_COPYABLE(VmPortAllocation);
65
66 VmPortAllocation(uint16_t port, int Family, int Protocol, std::weak_ptr<VmPortReservations> reservations);
67 VmPortAllocation(VmPortAllocation&& Other);
68 ~VmPortAllocation();
69
70 VmPortAllocation& operator=(VmPortAllocation&& Other);
71
72 void Reset();
73 void Release();
74 uint16_t Port() const;
75 int Family() const;
76 int Protocol() const;
77
78 private:
79 uint16_t m_port{};
80 int m_family{};
81 int m_protocol{};
82 std::weak_ptr<VmPortReservations> m_reservations;
83 };
84
85 struct VMPortMapping
86 {
87 NON_COPYABLE(VMPortMapping);
88
89 VMPortMapping(int Protocol, int Family, uint16_t Port, const char* Address);
90 ~VMPortMapping();
91
92 VMPortMapping(VMPortMapping&& Other);
93 VMPortMapping& operator=(VMPortMapping&& Other);
94
95 void AssignVmPort(const std::shared_ptr<VmPortAllocation>& Port);
96
97 void Unmap();
98 void Release();
99 bool IsLocalhost() const;
100 bool IsIPv6() const;
101 std::string BindingAddressString() const;
102 void Attach(WSLCVirtualMachine& Vm);
103 void Detach();
104 uint16_t HostPort() const;
105 void SetHostPort(uint16_t port);
106
107 static VMPortMapping LocalhostTcpMapping(int Family, uint16_t WindowsPort);
108 static VMPortMapping FromWSLCPortMapping(const ::WSLCPortMapping& Mapping);
109 static VMPortMapping FromContainerMetaData(const wslc::WSLCPortMapping& Mapping);
110
111 int Protocol{};
112 std::shared_ptr<VmPortAllocation> VmPort;
113 SOCKADDR_INET BindAddress{};
114
115 private:
116 static SOCKADDR_INET ParseBindingAddress(int Family, uint16_t Port, const char* Address);
117
118 WSLCVirtualMachine* Vm{};
119 };
120
121 class WSLCVirtualMachine
122 {
123 public:
124 static inline const char* c_gpuLibrariesPath = "/usr/lib/wsl/lib";
125 static inline const char* c_gpuDriversPath = "/usr/lib/wsl/drivers";
126
127 // Path where the guest init writes the BuildKit source-policy JSON when the
128 // WSLContainerRegistryAllowlist policy is configured. /run is tmpfs, so the file
129 // disappears on VM shutdown.
130 static inline const char* c_buildKitPolicyPath = "/run/wsl/buildkit-policy.json";
131
132 // Snapshot of the WSLContainerRegistryAllowlist policy taken at VM boot. A read failure
133 // throws from Initialize; NotConfigured/Configured are the only states BuildImage sees.
134 enum class BuildKitPolicyState
135 {
136 NotConfigured,
137 Configured
138 };
139
140 struct ConnectedSocket
141 {
142 int Fd = -1;
143 wil::unique_socket Socket;
144 };
145
146 using TPrepareCommandLine = std::function<void(const std::vector<ConnectedSocket>&)>;
147
148 // Invoked when a Linux process crash dump has been written to disk. The arguments mirror
149 // ICrashDumpCallback::OnCrashDump. The VM owns producing crash events; the session owns
150 // fanning them out to any registered COM callbacks.
151 using TOnCrashDump =
152 std::function<void(const std::wstring& DumpPath, const std::string& ProcessName, ULONG Pid, ULONG Signal, ULONGLONG Timestamp)>;
153
154 WSLCVirtualMachine(_In_ IWSLCVirtualMachine* Vm, _In_ const WSLCSessionInitSettings* Settings, _In_ HANDLE SessionTerminatingEvent, _In_ TOnCrashDump&& OnCrashDump);
155 ~WSLCVirtualMachine();
156
157 void Initialize();
158
159 void MapPort(VMPortMapping& Mapping);
160 void UnmapPort(VMPortMapping& Mapping);
161 void Unmount(_In_ const char* Path);
162
163 HRESULT MountWindowsFolder(_In_ LPCWSTR WindowsPath, _In_ LPCSTR LinuxPath, _In_ BOOL ReadOnly);
164 HRESULT UnmountWindowsFolder(_In_ LPCSTR LinuxPath);
165
166 BuildKitPolicyState GetBuildKitPolicyState() const
167 {
168 return m_buildKitPolicyState;
169 }
170
171 void Signal(_In_ LONG Pid, _In_ int Signal);
172
173 void OnProcessReleased(int Pid);
174 void OnSessionTerminated();
175
176 std::shared_ptr<VmPortAllocation> TryAllocatePort(uint16_t Port, int Family, int Protocol);
177 std::shared_ptr<VmPortAllocation> AllocatePort(int Family, int Protocol);
178
179 Microsoft::WRL::ComPtr<WSLCProcess> CreateLinuxProcess(
180 _In_ LPCSTR Executable,
181 _In_ const WSLCProcessOptions& Options,
182 _In_ ULONG TtyRows = 0,
183 _In_ ULONG TtyColumns = 0,
184 int* Errno = nullptr,
185 const TPrepareCommandLine& PrepareCommandLine = [](const auto&) {});
186
187 std::pair<ULONG, std::string> AttachDisk(_In_ PCWSTR Path, _In_ BOOL ReadOnly);
188 void DetachDisk(_In_ ULONG Lun);
189 void Ext4Format(_In_ const std::string& Device, _In_ std::optional<uint32_t> Uid = std::nullopt, _In_ std::optional<uint32_t> Gid = std::nullopt);
190 void Mount(_In_ LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags);
191 void RemoveDirectory(_In_ const std::string& Path);
192 std::vector<std::string> ListDirectory(_In_ const std::string& Path);
193
194 wil::unique_socket ConnectUnixSocket(_In_ const char* Path);
195 std::tuple<int32_t, int32_t, wsl::shared::SocketChannel> Fork(enum WSLC_FORK::ForkType Type);
196
197 // Returns an event that is signaled when the VM is being terminated.
198 // Use this to cancel pending operations.
199 HANDLE TerminatingEvent() const
200 {
201 return m_vmTerminatingEvent.get();
202 }
203
204 // Retrieves the cached termination reason and details from the underlying VM.
205 HRESULT GetTerminationReason(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details) const
206 {
207 return m_vm->GetTerminationReason(Reason, Details);
208 }
209
210 GUID VmId() const
211 {
212 return m_vmId;
213 }
214
215 bool FeatureEnabled(WSLCFeatureFlags Flag) const;
216
217 WSLCNetworkingMode NetworkingMode() const;
218
219 // True when port forwarding goes through the userspace wslrelay path (NAT mode, or Consomme with
220 // the wslrelay feature flag). That relay only supports TCP localhost mappings.
221 bool UseWslRelayPortForwarding() const;
222
223 private:
224 void MapRelayPort(_In_ int Family, _In_ unsigned short WindowsPort, _In_ unsigned short LinuxPort, _In_ bool Remove);
225
226 // Initial setup during Connect()
227 void ConfigureNetworking();
228
229 // Queries the guest kernel for per-VM capabilities (currently the hv_pci swiotlb pool
230 // reserved at boot) and forwards them to the service before virtio devices are created.
231 // Called after the root filesystem is mounted.
232 void ReadGuestCapabilities();
233
234 // Reads the WSLContainerRegistryAllowlist policy from the registry and, when configured,
235 // hands the BuildKit source-policy JSON to the guest init for materialisation. Cached in
236 // m_buildKitPolicyState for BuildImage to consult per build.
237 void ConfigureBuildKitPolicy();
238
239 static void Mount(wsl::shared::SocketChannel& Channel, LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags);
240 static void MountModules(wsl::shared::SocketChannel& Channel, _In_ LPCSTR Source);
241 static void MountVirtioFsChild(
242 wsl::shared::SocketChannel& Channel, _In_ LPCSTR Source, _In_ LPCSTR ChildName, _In_ LPCSTR Target, _In_ LPCSTR Options, _In_ ULONG Flags);
243 void MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_ LPCSTR DriversMountpoint);
244
245 Microsoft::WRL::ComPtr<WSLCProcess> CreateLinuxProcessImpl(
246 _In_ LPCSTR Executable,
247 _In_ const WSLCProcessOptions& Options,
248 _In_ const std::vector<WSLCProcessFd>& Fds = {},
249 _In_ ULONG TtyRows = 0,
250 _In_ ULONG TtyColumns = 0,
251 int* Errno = nullptr,
252 const TPrepareCommandLine& PrepareCommandLine = [](const auto&) {});
253
254 std::tuple<int32_t, int32_t, wsl::shared::SocketChannel> Fork(
255 wsl::shared::SocketChannel& Channel, enum WSLC_FORK::ForkType Type, ULONG TtyRows = 0, ULONG TtyColumns = 0);
256 int32_t ExpectClosedChannelOrError(wsl::shared::SocketChannel& Channel);
257
258 ConnectedSocket ConnectSocket(wsl::shared::SocketChannel& Channel, int32_t Fd);
259 std::string GetVhdDevicePath(ULONG Lun);
260 void LaunchPortRelay();
261
262 HRESULT MountWindowsFolderImpl(_In_ LPCWSTR WindowsPath, _In_ LPCSTR LinuxPath, _In_ WSLCMountFlags Flags = WSLCMountFlagsNone);
263
264 void WatchForExitedProcesses(wsl::shared::SocketChannel& Channel);
265
266 void CollectCrashDumps(wil::unique_socket&& listenSocket);
267
268 struct AttachedDisk
269 {
270 std::filesystem::path Path;
271 std::string Device;
272 };
273
274 // IWSLCVirtualMachine for privileged operations on this VM
275 wil::com_ptr<IWSLCVirtualMachine> m_vm;
276
277 WSLCFeatureFlags m_featureFlags{};
278 WSLCNetworkingMode m_networkingMode{};
279 ULONG m_bootTimeoutMs{};
280
281 std::string m_rootVhdType;
282
283 // Invoked by the crash dump collection thread after a crash dump is fully written.
284 // Supplied by the session, which fans out to any registered ICrashDumpCallback subscribers.
285 TOnCrashDump m_onCrashDump;
286
287 std::thread m_processExitThread;
288 std::thread m_crashDumpThread;
289
290 std::shared_ptr<VmPortReservations> m_reservations = std::make_shared<VmPortReservations>();
291
292 GUID m_vmId{};
293
294 std::mutex m_trackedProcessesLock;
295 std::vector<std::weak_ptr<VMProcessControl>> m_trackedProcesses;
296
297 wil::unique_event m_vmTerminatingEvent{wil::EventOptions::ManualReset};
298 HANDLE m_sessionTerminatingEvent{};
299
300 wsl::shared::SocketChannel m_initChannel;
301 DWORD m_initChannelTimeout = 30 * 1000;
302
303 // Swiotlb pool reserved by the guest kernel (zero when the kernel lacks the WSL patch).
304 uint64_t m_hvPciSwiotlbBase = 0;
305 uint64_t m_hvPciSwiotlbSize = 0;
306
307 BuildKitPolicyState m_buildKitPolicyState{BuildKitPolicyState::NotConfigured};
308
309 // Job object that terminates child processes (wslrelay.exe) when the VM shuts down.
310 // Declared before the port relay pipes so it is destroyed after them: any remaining
311 // wslrelay.exe is given the chance to exit via the closed pipes / signaled terminating
312 // event before the job-close kill kicks in.
313 wil::unique_handle m_processJobObject;
314
315 wil::unique_handle m_portRelayChannelRead;
316 wil::unique_handle m_portRelayChannelWrite;
317
318 std::map<ULONG, AttachedDisk> m_attachedDisks;
319 std::map<std::string, GUID> m_mountedWindowsFolders;
320
321 std::recursive_mutex m_lock;
322 std::mutex m_portRelaylock;
323 };
324 } // namespace wsl::windows::service::wslc