Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WSL support via UnixSocket #14

Merged
merged 4 commits into from
Jan 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,5 @@ csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true

# starting to convert to 4 spaces
[*WindowsOpenSshPipe*.cs]
[*{WindowsOpenSshPipe,WslSocket,UnixDomainSocketEndPoint}*.cs]
indent_size = 4
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

## Added
- Added this changelog.
- Added PuTTY private key v3 support
- Added PuTTY private key v3 support.
- Added Window UNIX socket for WSL `ssh-agent` support.

## Fixed
- Fixed using incorrect unmanaged memory free function in `PagentClent.SendMessage()`.
Expand Down
27 changes: 27 additions & 0 deletions SshAgentLib/Microsoft/UnixDomainSocketEndPoint.Windows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Net.Sockets
{
/// <summary>Represents a Unix Domain Socket endpoint as a path.</summary>
public sealed partial class UnixDomainSocketEndPoint : EndPoint
{
#pragma warning disable CA1802 // on Unix these need to be static readonly rather than const, so we do the same on Windows for consistency
private static readonly int s_nativePathOffset = 2; // sizeof(sun_family)
private static readonly int s_nativePathLength = 108; // sizeof(sun_path)
private static readonly int s_nativeAddressSize = s_nativePathOffset + s_nativePathLength; // sizeof(sockaddr_un)
#pragma warning restore CA1802

private SocketAddress CreateSocketAddressForSerialize() =>
new SocketAddress(AddressFamily.Unix, s_nativeAddressSize);

// from afunix.h:
//#define UNIX_PATH_MAX 108
//typedef struct sockaddr_un
//{
// ADDRESS_FAMILY sun_family; /* AF_UNIX */
// char sun_path[UNIX_PATH_MAX]; /* pathname */
//}
//SOCKADDR_UN, *PSOCKADDR_UN;
}
}
161 changes: 161 additions & 0 deletions SshAgentLib/Microsoft/UnixDomainSocketEndPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Text;
using System.IO;

namespace System.Net.Sockets
{
/// <summary>Represents a Unix Domain Socket endpoint as a path.</summary>
public sealed partial class UnixDomainSocketEndPoint : EndPoint
{
private const AddressFamily EndPointAddressFamily = AddressFamily.Unix;

private readonly string _path;
private readonly byte[] _encodedPath;

// Tracks the file Socket should delete on Dispose.
internal string BoundFileName { get; }

public UnixDomainSocketEndPoint(string path)
: this(path, null)
{ }

private UnixDomainSocketEndPoint(string path, string boundFileName)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}

BoundFileName = boundFileName;

// Pathname socket addresses should be null-terminated.
// Linux abstract socket addresses start with a zero byte, they must not be null-terminated.
bool isAbstract = IsAbstract(path);
int bufferLength = Encoding.UTF8.GetByteCount(path);
if (!isAbstract)
{
// for null terminator
bufferLength++;
}

if (path.Length == 0 || bufferLength > s_nativePathLength)
{
const string ArgumentOutOfRange_PathLengthInvalid =
"The path '{0}' is of an invalid length for use with domain sockets on this platform. The length must be between 1 and {1} characters, inclusive.";

throw new ArgumentOutOfRangeException(
nameof(path), path,
string.Format(ArgumentOutOfRange_PathLengthInvalid, path, s_nativePathLength));
//SR.Format(SR.ArgumentOutOfRange_PathLengthInvalid, path, s_nativePathLength));
}

_path = path;
_encodedPath = new byte[bufferLength];
int bytesEncoded = Encoding.UTF8.GetBytes(path, 0, path.Length, _encodedPath, 0);
Debug.Assert(bufferLength - (isAbstract ? 0 : 1) == bytesEncoded);

// FIXME: see https://github.com/dotnet/runtime/blob/f85ea976f81945ea18cd5dc71959cccecdc93cd2/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs#L14
//if (!Socket.OSSupportsUnixDomainSockets)
//{
// throw new PlatformNotSupportedException();
//}
}

internal static int MaxAddressSize => s_nativeAddressSize;

internal UnixDomainSocketEndPoint(SocketAddress socketAddress)
{
if (socketAddress == null)
{
throw new ArgumentNullException(nameof(socketAddress));
}

if (socketAddress.Family != EndPointAddressFamily ||
socketAddress.Size > s_nativeAddressSize)
{
throw new ArgumentOutOfRangeException(nameof(socketAddress));
}

if (socketAddress.Size > s_nativePathOffset)
{
_encodedPath = new byte[socketAddress.Size - s_nativePathOffset];
for (int i = 0; i < _encodedPath.Length; i++)
{
_encodedPath[i] = socketAddress[s_nativePathOffset + i];
}

// Strip trailing null of pathname socket addresses.
int length = _encodedPath.Length;
if (!IsAbstract(_encodedPath))
{
// Since this isn't an abstract path, we're sure our first byte isn't 0.
while (_encodedPath[length - 1] == 0)
{
length--;
}
}
_path = Encoding.UTF8.GetString(_encodedPath, 0, length);
}
else
{
_encodedPath = Array.Empty<byte>();
_path = string.Empty;
}
}

public override SocketAddress Serialize()
{
SocketAddress result = CreateSocketAddressForSerialize();

for (int index = 0; index < _encodedPath.Length; index++)
{
result[s_nativePathOffset + index] = _encodedPath[index];
}

return result;
}

public override EndPoint Create(SocketAddress socketAddress) => new UnixDomainSocketEndPoint(socketAddress);

public override AddressFamily AddressFamily => EndPointAddressFamily;

public override string ToString()
{
bool isAbstract = IsAbstract(_path);
if (isAbstract)
{
// return string.Concat("@", _path.AsSpan(1));
return "@" + _path.Substring(1);
}
else
{
return _path;
}
}

internal UnixDomainSocketEndPoint CreateBoundEndPoint()
{
if (IsAbstract(_path))
{
return this;
}
return new UnixDomainSocketEndPoint(_path, Path.GetFullPath(_path));
}

internal UnixDomainSocketEndPoint CreateUnboundEndPoint()
{
if (IsAbstract(_path) || BoundFileName is null)
{
return this;
}
return new UnixDomainSocketEndPoint(_path, null);
}

private static bool IsAbstract(string path) => path.Length > 0 && path[0] == '\0';

private static bool IsAbstract(byte[] encodedPath) => encodedPath.Length > 0 && encodedPath[0] == 0;
}
}
38 changes: 36 additions & 2 deletions SshAgentLib/PageantAgent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//
//
// PageantAgent.cs
//
// Author(s): David Lechner <[email protected]>
Expand Down Expand Up @@ -53,6 +53,7 @@ public class PageantAgent : Agent
const int ERROR_CLASS_ALREADY_EXISTS = 1410;
const int WM_COPYDATA = 0x004A;
const int WSAECONNABORTED = 10053;
const int WSAECONNRESET = 10054;

/* From PuTTY source code */

Expand All @@ -70,6 +71,7 @@ public class PageantAgent : Agent
object lockObject = new object();
CygwinSocket cygwinSocket;
MsysSocket msysSocket;
WslSocket wslSocket;
WindowsOpenSshPipe opensshPipe;
Thread winThread;

Expand Down Expand Up @@ -287,6 +289,36 @@ public void StopMsysSocket()
msysSocket = null;
}

/// <summary>
/// Starts a wsl style socket that can be used by the ssh program
/// that comes with wsl.
/// </summary>
/// <param name="path">The path to the socket file that will be created.</param>
public void StartWslSocket(string path)
{
if (disposed) {
throw new ObjectDisposedException("PagentAgent");
}
if (wslSocket != null) {
mehrdadn marked this conversation as resolved.
Show resolved Hide resolved
return;
}
// only overwrite a file if it looks like a WslSocket file.
if (File.Exists(path) && WslSocket.TestFile(path)) {
File.Delete(path);
}
wslSocket = new WslSocket(path, connectionHandler);
}

public void StopWslSocket()
{
if (disposed)
throw new ObjectDisposedException("PagentAgent");
if (wslSocket == null)
return;
wslSocket.Dispose();
wslSocket = null;
}

public void StartWindowsOpenSshPipe()
{
if (disposed) {
Expand Down Expand Up @@ -353,6 +385,7 @@ private void RunWindowInNewAppcontext()
// make sure socket files are cleaned up when we stop.
StopCygwinSocket();
StopMsysSocket();
StopWslSocket();
StopWindowsOpenSshPipe();

if (hwnd != IntPtr.Zero) {
Expand Down Expand Up @@ -455,7 +488,8 @@ void connectionHandler(Stream stream, Process process)
}
} catch (IOException ex) {
var socketException = ex.InnerException as SocketException;
if (socketException != null && socketException.ErrorCode == WSAECONNABORTED) {
if (socketException != null && (
socketException.ErrorCode == WSAECONNABORTED || socketException.ErrorCode == WSAECONNRESET)) {
// expected error
return;
}
Expand Down
6 changes: 6 additions & 0 deletions SshAgentLib/SshAgentLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@
<Compile Include="Crypto\Ed25519PublicKeyParameter.cs" />
<Compile Include="Crypto\Ed25519Signer.cs" />
<Compile Include="Crypto\SaltParseException.cs" />
<Compile Include="Microsoft\UnixDomainSocketEndPoint.cs" />
<Compile Include="Microsoft\UnixDomainSocketEndPoint.Windows.cs" />
<Compile Include="WslSocket.cs" />
<Compile Include="WindowsOpenSshPipe.cs" />
<Compile Include="MsysSocket .cs" />
<Compile Include="CygwinSocket.cs" />
Expand Down Expand Up @@ -159,6 +162,9 @@
<PackageReference Include="Portable.BouncyCastle">
<Version>1.8.1.3</Version>
</PackageReference>
<PackageReference Include="System.Net.Sockets">
<Version>4.3.0</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down
Loading