Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
aac55ae
Add port mappings to container list output
beena352 Mar 16, 2026
8f9e2f3
Fix port validation and add Protocol field test
beena352 Mar 16, 2026
1c3c77f
Add scope_exit cleanup for nested port memory in test
beena352 Mar 16, 2026
0a24110
Merge branch 'feature/wsl-for-apps' of https://github.com/microsoft/W…
beena352 Mar 22, 2026
fd8a3ff
Resolve merge conflicts
beena352 Mar 23, 2026
f6a5ab1
Apply copilot feedback
beena352 Mar 23, 2026
a053a1f
Apply PR feedback
beena352 Mar 23, 2026
e9a4fce
Merge branch 'feature/wsl-for-apps' of https://github.com/microsoft/W…
beena352 Mar 24, 2026
3119329
Remove Family field from PortInformation
beena352 Mar 24, 2026
6bbbc98
Only show port mappings for running containers
beena352 Mar 24, 2026
dc991ae
Merge branch 'feature/wsl-for-apps' into user/beenachauhan/container-…
beena352 Mar 24, 2026
2cc1ef2
Format
beena352 Mar 24, 2026
fb45413
Merge branch 'feature/wsl-for-apps' into user/beenachauhan/container-…
beena352 Mar 25, 2026
4584d93
Simplify port mappings in ListContainers by removing nested COM alloc…
beena352 Mar 25, 2026
d454c39
Fix macro mismatch in ContainerInformation
beena352 Mar 25, 2026
bab2a0a
Update serialization for PortInformation
beena352 Mar 26, 2026
59baa1b
Address PR Feedback
beena352 Mar 27, 2026
9bc9d78
Merge remote-tracking branch 'origin/feature/wsl-for-apps' into user/…
beena352 Mar 27, 2026
5e59835
Copilot feedback
beena352 Mar 27, 2026
42fa290
rename ListContainers parameter Images->Containers
beena352 Mar 28, 2026
01a56ce
PR Feedback
beena352 Mar 30, 2026
328edf1
correct static_assert for null terminator
beena352 Mar 30, 2026
f264bde
Merge branch 'feature/wsl-for-apps' into user/beenachauhan/container-…
beena352 Mar 31, 2026
6d0b656
fix bad merge
beena352 Mar 31, 2026
9453346
Merge branch 'feature/wsl-for-apps' into user/beenachauhan/container-…
beena352 Mar 31, 2026
515cfa0
Merge remote-tracking branch 'origin/feature/wsl-for-apps' into user/…
beena352 Mar 31, 2026
1277e9f
Merge branch 'user/beenachauhan/container-port-mappings' of https://g…
beena352 Mar 31, 2026
8fd06f9
resolve merge conflicts
beena352 Apr 1, 2026
7fe60a3
Merge branch 'feature/wsl-for-apps' into user/beenachauhan/container-…
OneBlue Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/windows/WslcSDK/wslcsdk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ try

// TODO: Consider using standard protocol numbers instead of our own enum.
convertedPort.Protocol = internalPort.protocol == WSLC_PORT_PROTOCOL_TCP ? IPPROTO_TCP : IPPROTO_UDP;
convertedPort.BindingAddress = "127.0.0.1";
strcpy_s(convertedPort.BindingAddress, "127.0.0.1");
Comment thread
beena352 marked this conversation as resolved.
}
containerOptions.Ports = convertedPorts.get();
containerOptions.PortsCount = static_cast<ULONG>(internalContainerSettings->portsCount);
Expand Down
20 changes: 17 additions & 3 deletions src/windows/common/WSLCContainerLauncher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,27 @@ void WSLCContainerLauncher::AddPort(uint16_t WindowsPort, uint16_t ContainerPort
{
THROW_HR_IF(E_INVALIDARG, Family != AF_INET && Family != AF_INET6);

auto& inserted = m_bindingAddressStorage.emplace_back(BindingAddress.value_or(Family == AF_INET ? "127.0.0.1" : "::1"));
m_ports.emplace_back(WSLCPortMapping{
WSLCPortMapping port{
.HostPort = WindowsPort,
.ContainerPort = ContainerPort,
.Family = Family,
.Protocol = Protocol,
.BindingAddress = inserted.c_str()});
};

if (BindingAddress.has_value())
{
THROW_HR_IF(E_INVALIDARG, BindingAddress->size() > WSLC_MAX_BINDING_ADDRESS_LENGTH);
THROW_HR_IF_MSG(
E_INVALIDARG, strcpy_s(port.BindingAddress, BindingAddress->c_str()) != 0, "Invalid address: %hs", BindingAddress->c_str());
}
else
{
Comment thread
beena352 marked this conversation as resolved.
static_assert(sizeof("127.0.0.1") <= WSLC_MAX_BINDING_ADDRESS_LENGTH + 1, "Default IPv4 binding address too long");
static_assert(sizeof("::1") <= WSLC_MAX_BINDING_ADDRESS_LENGTH + 1, "Default IPv6 binding address too long");
THROW_HR_IF(E_INVALIDARG, strcpy_s(port.BindingAddress, Family == AF_INET ? "127.0.0.1" : "::1") != 0);
}

m_ports.push_back(port);
}

void WSLCContainerLauncher::SetName(std::string&& Name)
Expand Down
1 change: 0 additions & 1 deletion src/windows/common/WSLCContainerLauncher.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ class WSLCContainerLauncher : private WSLCProcessLauncher
private:
std::string m_image;
std::string m_name;
std::deque<std::string> m_bindingAddressStorage;
std::vector<WSLCPortMapping> m_ports;
std::vector<WSLCVolume> m_volumes;
std::vector<WSLCNamedVolume> m_namedVolumes;
Expand Down
15 changes: 13 additions & 2 deletions src/windows/service/inc/wslc.idl
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ cpp_quote("#endif")
#define WSLC_MAX_IMAGE_NAME_LENGTH 255
#define WSLC_MAX_VOLUME_NAME_LENGTH 255
#define WSLC_CONTAINER_ID_LENGTH 64
#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45

cpp_quote("#define WSLC_MAX_CONTAINER_NAME_LENGTH 255")
cpp_quote("#define WSLC_MAX_IMAGE_NAME_LENGTH 255")
cpp_quote("#define WSLC_MAX_VOLUME_NAME_LENGTH 255")
cpp_quote("#define WSLC_CONTAINER_ID_LENGTH 64")
cpp_quote("#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45")

typedef
struct _WSLCVersion {
Expand Down Expand Up @@ -198,7 +200,7 @@ typedef struct _WSLCPortMapping
USHORT ContainerPort;
int Family;
int Protocol;
[string] LPCSTR BindingAddress;
char BindingAddress[WSLC_MAX_BINDING_ADDRESS_LENGTH + 1];
Comment thread
beena352 marked this conversation as resolved.
} WSLCPortMapping;

Comment thread
beena352 marked this conversation as resolved.
typedef struct _WSLCTmpfsMount
Expand Down Expand Up @@ -292,6 +294,12 @@ typedef struct _WSLCContainerEntry
WSLCContainerState State;
} WSLCContainerEntry;

typedef struct _WSLCContainerPortMapping
{
WSLCContainerId Id;
WSLCPortMapping PortMapping;
} WSLCContainerPortMapping;

typedef [system_handle(sh_file)] HANDLE FILE_HANDLE;
typedef [system_handle(sh_pipe)] HANDLE PIPE_HANDLE;
typedef [system_handle(sh_socket)] HANDLE SOCKET_HANDLE;
Expand Down Expand Up @@ -577,7 +585,10 @@ interface IWSLCSession : IUnknown
// Container management.
HRESULT CreateContainer([in] const WSLCContainerOptions* Options, [out] IWSLCContainer** Container);
HRESULT OpenContainer([in, ref] LPCSTR Id, [out] IWSLCContainer** Container);
HRESULT ListContainers([out, size_is(, *Count)] WSLCContainerEntry** Images, [out] ULONG* Count);
HRESULT ListContainers([out, size_is(, *Count)] WSLCContainerEntry** Containers,
Comment thread
beena352 marked this conversation as resolved.
[out] ULONG* Count,
[out, size_is(, *PortsCount)] WSLCContainerPortMapping** Ports,
[out] ULONG* PortsCount);
Comment thread
beena352 marked this conversation as resolved.
Comment thread
beena352 marked this conversation as resolved.
Comment thread
beena352 marked this conversation as resolved.
HRESULT PruneContainers([in, unique, size_is(FiltersCount)] WSLCContainerPruneFilter* Filters, [in] DWORD FiltersCount, [in] ULONGLONG Until, [out] WSLCPruneContainersResults* Result);

// Create a process at the VM level. This is meant for debugging.
Expand Down
13 changes: 12 additions & 1 deletion src/windows/wslc/services/ContainerModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ struct KillContainerOptions
int Signal = WSLCSignalSIGKILL;
};

struct PortInformation
Comment thread
beena352 marked this conversation as resolved.
{
uint16_t HostPort{};
uint16_t ContainerPort{};
int Protocol{}; // IP protocol number (e.g., IPPROTO_TCP or IPPROTO_UDP)
std::string BindingAddress;

NLOHMANN_DEFINE_TYPE_INTRUSIVE(PortInformation, HostPort, ContainerPort, Protocol, BindingAddress);
};

struct ContainerInformation
{
std::string Id;
Expand All @@ -67,8 +77,9 @@ struct ContainerInformation
WSLCContainerState State;
ULONGLONG StateChangedAt{};
ULONGLONG CreatedAt{};
std::vector<PortInformation> Ports;

NLOHMANN_DEFINE_TYPE_INTRUSIVE(ContainerInformation, Id, Name, Image, State, StateChangedAt, CreatedAt);
NLOHMANN_DEFINE_TYPE_INTRUSIVE(ContainerInformation, Id, Name, Image, State, StateChangedAt, CreatedAt, Ports);
};
Comment thread
beena352 marked this conversation as resolved.

struct EnvironmentVariable
Expand Down
53 changes: 52 additions & 1 deletion src/windows/wslc/services/ContainerService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(
return std::move(*runningContainer);
}

static PortInformation PortInformationFromWSLCPortMapping(const WSLCPortMapping& mapping)
{
return PortInformation{
.HostPort = mapping.HostPort,
.ContainerPort = mapping.ContainerPort,
.Protocol = static_cast<int>(mapping.Protocol),
.BindingAddress = mapping.BindingAddress,
};
}

std::wstring ContainerService::FormatRelativeTime(ULONGLONG timestamp)
{
constexpr LONGLONG SecondsPerMinute = std::chrono::duration_cast<std::chrono::seconds>(1min).count();
Expand Down Expand Up @@ -256,6 +266,36 @@ std::wstring ContainerService::ContainerStateToString(WSLCContainerState state,
return std::format(L"{} {}", stateString, FormatRelativeTime(stateChangedAt));
}

std::wstring ContainerService::FormatPorts(WSLCContainerState state, const std::vector<PortInformation>& ports)
{
if (state != WslcContainerStateRunning || ports.empty())
{
return L"";
}

std::wstring result;
for (size_t i = 0; i < ports.size(); ++i)
{
const auto& port = ports[i];

std::wstring hostIp = wsl::shared::string::MultiByteToWide(port.BindingAddress);

std::wstring protocol = (port.Protocol == IPPROTO_TCP) ? L"tcp"
: (port.Protocol == IPPROTO_UDP) ? L"udp"
: std::format(L"{}", port.Protocol);

if (i > 0)
{
result += L", ";
}

result += std::format(
L"{}:{}->{}/{}", (hostIp.find(L':') != std::wstring::npos) ? std::format(L"[{}]", hostIp) : hostIp, port.HostPort, port.ContainerPort, protocol);
}

return result;
}

int ContainerService::Run(Session& session, const std::string& image, ContainerOptions runOptions)
{
// Create the container
Expand Down Expand Up @@ -339,7 +379,9 @@ std::vector<ContainerInformation> ContainerService::List(Session& session)
{
std::vector<ContainerInformation> result;
wil::unique_cotaskmem_array_ptr<WSLCContainerEntry> containers;
THROW_IF_FAILED(session.Get()->ListContainers(&containers, containers.size_address<ULONG>()));
wil::unique_cotaskmem_array_ptr<WSLCContainerPortMapping> ports;
THROW_IF_FAILED(session.Get()->ListContainers(&containers, containers.size_address<ULONG>(), &ports, ports.size_address<ULONG>()));

for (const auto& current : containers)
{
ContainerInformation entry;
Expand All @@ -349,6 +391,15 @@ std::vector<ContainerInformation> ContainerService::List(Session& session)
entry.Id = current.Id;
entry.StateChangedAt = current.StateChangedAt;
entry.CreatedAt = current.CreatedAt;

for (const auto& port : ports)
{
if (strcmp(port.Id, current.Id) == 0)
{
entry.Ports.push_back(PortInformationFromWSLCPortMapping(port.PortMapping));
}
}
Comment thread
beena352 marked this conversation as resolved.
Comment thread
beena352 marked this conversation as resolved.
Comment thread
beena352 marked this conversation as resolved.

result.emplace_back(std::move(entry));
}
Comment thread
beena352 marked this conversation as resolved.

Expand Down
1 change: 1 addition & 0 deletions src/windows/wslc/services/ContainerService.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct ContainerService
{
static std::wstring ContainerStateToString(WSLCContainerState state, ULONGLONG stateChangedAt = 0);
static std::wstring FormatRelativeTime(ULONGLONG timestamp);
static std::wstring FormatPorts(WSLCContainerState state, const std::vector<models::PortInformation>& ports);
static int Attach(models::Session& session, const std::string& id);
static int Run(models::Session& session, const std::string& image, models::ContainerOptions options);
static models::CreateContainerResult Create(models::Session& session, const std::string& image, models::ContainerOptions options);
Expand Down
17 changes: 10 additions & 7 deletions src/windows/wslc/tasks/ContainerTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,15 @@ void ListContainers(CLIExecutionContext& context)
bool trunc = !context.Args.Contains(ArgType::NoTrunc);

// Create table with or without column limits based on --no-trunc flag
auto table = trunc ? wsl::windows::wslc::TableOutput<5>(
{{{L"CONTAINER ID", {Config::NoLimit, 12, false}},
{L"NAME", {Config::NoLimit, 20, true}},
{L"IMAGE", {Config::NoLimit, 20, false}},
{L"CREATED", {Config::NoLimit, Config::NoLimit, false}},
{L"STATUS", {Config::NoLimit, Config::NoLimit, false}}}})
: wsl::windows::wslc::TableOutput<5>({L"CONTAINER ID", L"NAME", L"IMAGE", L"CREATED", L"STATUS"});
auto table =
trunc ? wsl::windows::wslc::TableOutput<6>(
{{{L"CONTAINER ID", {Config::NoLimit, 12, false}},
{L"NAME", {Config::NoLimit, 20, true}},
{L"IMAGE", {Config::NoLimit, 20, false}},
{L"CREATED", {Config::NoLimit, Config::NoLimit, false}},
{L"STATUS", {Config::NoLimit, Config::NoLimit, false}},
{L"PORTS", {Config::NoLimit, Config::NoLimit, false}}}})
: wsl::windows::wslc::TableOutput<6>({L"CONTAINER ID", L"NAME", L"IMAGE", L"CREATED", L"STATUS", L"PORTS"});

// Add each container as a row
for (const auto& container : containers)
Expand All @@ -158,6 +160,7 @@ void ListContainers(CLIExecutionContext& context)
MultiByteToWide(container.Image),
ContainerService::FormatRelativeTime(container.CreatedAt),
ContainerService::ContainerStateToString(container.State, container.StateChangedAt),
ContainerService::FormatPorts(container.State, container.Ports),
});
}

Expand Down
17 changes: 17 additions & 0 deletions src/windows/wslcsession/WSLCContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,23 @@ const std::string& WSLCContainerImpl::Name() const noexcept
return m_name;
}

std::vector<WSLCPortMapping> WSLCContainerImpl::GetPorts() const
{
auto lock = m_lock.lock_shared();
if (m_state != WslcContainerStateRunning)
{
return {};
}

std::vector<WSLCPortMapping> result;
result.reserve(m_mappedPorts.size());
for (const auto& port : m_mappedPorts)
{
result.push_back(port.Serialize());
}
return result;
Comment thread
beena352 marked this conversation as resolved.
}

void WSLCContainerImpl::GetStateChangedAt(ULONGLONG* Result)
{
auto lock = m_lock.lock_shared();
Expand Down
1 change: 1 addition & 0 deletions src/windows/wslcsession/WSLCContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class WSLCContainerImpl
const std::string& Image() const noexcept;
const std::string& Name() const noexcept;
WSLCContainerState State() const noexcept;
std::vector<WSLCPortMapping> GetPorts() const;

__requires_lock_held(m_lock) void Transition(WSLCContainerState State, std::optional<std::uint64_t> stateChangedAt = std::nullopt) noexcept;

Expand Down
28 changes: 27 additions & 1 deletion src/windows/wslcsession/WSLCSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1417,13 +1417,15 @@ try
}
CATCH_RETURN();

HRESULT WSLCSession::ListContainers(WSLCContainerEntry** Containers, ULONG* Count)
HRESULT WSLCSession::ListContainers(WSLCContainerEntry** Containers, ULONG* Count, WSLCContainerPortMapping** Ports, ULONG* PortsCount)
try
{
COMServiceExecutionContext context;

*Count = 0;
*Containers = nullptr;
*Ports = nullptr;
*PortsCount = 0;
Comment thread
beena352 marked this conversation as resolved.

auto lock = m_lock.lock_shared();
std::lock_guard containersLock{m_containersLock};
Expand All @@ -1432,6 +1434,7 @@ try
std::erase_if(m_containers, [](const auto& e) { return e->State() == WslcContainerStateDeleted; });

auto output = wil::make_unique_cotaskmem<WSLCContainerEntry[]>(m_containers.size());
std::vector<WSLCContainerPortMapping> allPorts;

size_t index = 0;
for (const auto& e : m_containers)
Expand All @@ -1442,11 +1445,34 @@ try
e->GetState(&output[index].State);
e->GetStateChangedAt(&output[index].StateChangedAt);
e->GetCreatedAt(&output[index].CreatedAt);

for (const auto& port : e->GetPorts())
{
WSLCContainerPortMapping mapping{};
THROW_HR_IF(E_UNEXPECTED, strcpy_s(mapping.Id, e->ID().c_str()) != 0);
mapping.PortMapping.HostPort = port.HostPort;
mapping.PortMapping.ContainerPort = port.ContainerPort;
mapping.PortMapping.Family = port.Family;
mapping.PortMapping.Protocol = port.Protocol;
Comment thread
beena352 marked this conversation as resolved.
THROW_HR_IF(E_UNEXPECTED, port.BindingAddress.size() > WSLC_MAX_BINDING_ADDRESS_LENGTH);
THROW_HR_IF(E_UNEXPECTED, strcpy_s(mapping.PortMapping.BindingAddress, port.BindingAddress.c_str()) != 0);
allPorts.push_back(mapping);
}

index++;
}

*Count = static_cast<ULONG>(m_containers.size());
*Containers = output.release();

if (!allPorts.empty())
{
auto portsOutput = wil::make_unique_cotaskmem<WSLCContainerPortMapping[]>(allPorts.size());
memcpy(portsOutput.get(), allPorts.data(), allPorts.size() * sizeof(WSLCContainerPortMapping));
*PortsCount = static_cast<ULONG>(allPorts.size());
*Ports = portsOutput.release();
}

return S_OK;
}
CATCH_RETURN();
Expand Down
2 changes: 1 addition & 1 deletion src/windows/wslcsession/WSLCSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession
// Container management.
IFACEMETHOD(CreateContainer)(_In_ const WSLCContainerOptions* Options, _Out_ IWSLCContainer** Container) override;
IFACEMETHOD(OpenContainer)(_In_ LPCSTR Id, _In_ IWSLCContainer** Container) override;
IFACEMETHOD(ListContainers)(_Out_ WSLCContainerEntry** Images, _Out_ ULONG* Count) override;
IFACEMETHOD(ListContainers)(_Out_ WSLCContainerEntry** Containers, _Out_ ULONG* Count, _Out_ WSLCContainerPortMapping** Ports, _Out_ ULONG* PortsCount) override;
IFACEMETHOD(PruneContainers)(_In_opt_ WSLCContainerPruneFilter* Filters, _In_ DWORD FiltersCount, _In_ ULONGLONG Until, _Out_ WSLCPruneContainersResults* Result) override;

// VM management.
Expand Down
Loading