Skip to content

Commit

Permalink
Merge pull request #14 from mehrdadn/master
Browse files Browse the repository at this point in the history
Add WSL support via UnixSocket
  • Loading branch information
dlech authored Jan 29, 2022
2 parents ae6f8b9 + 7c85e3a commit 07c4d17
Show file tree
Hide file tree
Showing 9 changed files with 496 additions and 4 deletions.
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) {
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

0 comments on commit 07c4d17

Please sign in to comment.