Rename Shadowsocks.Proxy -> Shadowsocks.ForwardProxy
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using Shadowsocks.Util.Sockets;
|
||||
|
||||
namespace Shadowsocks.ForwardProxy
|
||||
{
|
||||
public class DirectConnect : IForwardProxy
|
||||
{
|
||||
private class FakeAsyncResult : IAsyncResult
|
||||
{
|
||||
public FakeAsyncResult(object state)
|
||||
{
|
||||
AsyncState = state;
|
||||
}
|
||||
|
||||
public bool IsCompleted { get; } = true;
|
||||
public WaitHandle AsyncWaitHandle { get; } = null;
|
||||
public object AsyncState { get; }
|
||||
public bool CompletedSynchronously { get; } = true;
|
||||
}
|
||||
|
||||
private class FakeEndPoint : EndPoint
|
||||
{
|
||||
public override AddressFamily AddressFamily { get; } = AddressFamily.Unspecified;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "null proxy";
|
||||
}
|
||||
}
|
||||
|
||||
private WrappedSocket _remote = new WrappedSocket();
|
||||
|
||||
public EndPoint LocalEndPoint => _remote.LocalEndPoint;
|
||||
|
||||
public EndPoint ProxyEndPoint { get; } = new FakeEndPoint();
|
||||
public EndPoint DestEndPoint { get; private set; }
|
||||
|
||||
public void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state)
|
||||
{
|
||||
// do nothing
|
||||
|
||||
var r = new FakeAsyncResult(state);
|
||||
callback?.Invoke(r);
|
||||
}
|
||||
|
||||
public void EndConnectProxy(IAsyncResult asyncResult)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state)
|
||||
{
|
||||
DestEndPoint = destEndPoint;
|
||||
|
||||
_remote.BeginConnect(destEndPoint, callback, state);
|
||||
}
|
||||
|
||||
public void EndConnectDest(IAsyncResult asyncResult)
|
||||
{
|
||||
_remote.EndConnect(asyncResult);
|
||||
_remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
|
||||
}
|
||||
|
||||
public void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
|
||||
object state)
|
||||
{
|
||||
_remote.BeginSend(buffer, offset, size, socketFlags, callback, state);
|
||||
}
|
||||
|
||||
public int EndSend(IAsyncResult asyncResult)
|
||||
{
|
||||
return _remote.EndSend(asyncResult);
|
||||
}
|
||||
|
||||
public void BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
|
||||
object state)
|
||||
{
|
||||
_remote.BeginReceive(buffer, offset, size, socketFlags, callback, state);
|
||||
}
|
||||
|
||||
public int EndReceive(IAsyncResult asyncResult)
|
||||
{
|
||||
return _remote.EndReceive(asyncResult);
|
||||
}
|
||||
|
||||
public void Shutdown(SocketShutdown how)
|
||||
{
|
||||
_remote.Shutdown(how);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_remote.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Shadowsocks.Controller;
|
||||
using Shadowsocks.Util.Sockets;
|
||||
|
||||
namespace Shadowsocks.ForwardProxy
|
||||
{
|
||||
public class HttpProxy : IForwardProxy
|
||||
{
|
||||
private class FakeAsyncResult : IAsyncResult
|
||||
{
|
||||
public readonly HttpState innerState;
|
||||
|
||||
private readonly IAsyncResult r;
|
||||
|
||||
public FakeAsyncResult(IAsyncResult orig, HttpState state)
|
||||
{
|
||||
r = orig;
|
||||
innerState = state;
|
||||
}
|
||||
|
||||
public bool IsCompleted => r.IsCompleted;
|
||||
public WaitHandle AsyncWaitHandle => r.AsyncWaitHandle;
|
||||
public object AsyncState => innerState.AsyncState;
|
||||
public bool CompletedSynchronously => r.CompletedSynchronously;
|
||||
}
|
||||
|
||||
private class HttpState
|
||||
{
|
||||
|
||||
public AsyncCallback Callback { get; set; }
|
||||
|
||||
public object AsyncState { get; set; }
|
||||
|
||||
public int BytesToRead;
|
||||
|
||||
public Exception ex { get; set; }
|
||||
}
|
||||
|
||||
public EndPoint LocalEndPoint => _remote.LocalEndPoint;
|
||||
public EndPoint ProxyEndPoint { get; private set; }
|
||||
public EndPoint DestEndPoint { get; private set; }
|
||||
|
||||
|
||||
private readonly WrappedSocket _remote = new WrappedSocket();
|
||||
|
||||
|
||||
public void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state)
|
||||
{
|
||||
ProxyEndPoint = remoteEP;
|
||||
|
||||
_remote.BeginConnect(remoteEP, callback, state);
|
||||
}
|
||||
|
||||
public void EndConnectProxy(IAsyncResult asyncResult)
|
||||
{
|
||||
_remote.EndConnect(asyncResult);
|
||||
_remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
|
||||
}
|
||||
|
||||
private const string HTTP_CRLF = "\r\n";
|
||||
private const string HTTP_CONNECT_TEMPLATE =
|
||||
"CONNECT {0} HTTP/1.1" + HTTP_CRLF +
|
||||
"Host: {0}" + HTTP_CRLF +
|
||||
"Proxy-Connection: keep-alive" + HTTP_CRLF +
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36" + HTTP_CRLF +
|
||||
"" + HTTP_CRLF; // End with an empty line
|
||||
|
||||
public void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state)
|
||||
{
|
||||
DestEndPoint = destEndPoint;
|
||||
string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint);
|
||||
|
||||
var b = Encoding.UTF8.GetBytes(request);
|
||||
|
||||
var st = new HttpState();
|
||||
st.Callback = callback;
|
||||
st.AsyncState = state;
|
||||
|
||||
_remote.BeginSend(b, 0, b.Length, 0, HttpRequestSendCallback, st);
|
||||
}
|
||||
|
||||
public void EndConnectDest(IAsyncResult asyncResult)
|
||||
{
|
||||
var state = ((FakeAsyncResult)asyncResult).innerState;
|
||||
|
||||
if (state.ex != null)
|
||||
{
|
||||
throw state.ex;
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
|
||||
object state)
|
||||
{
|
||||
_remote.BeginSend(buffer, offset, size, socketFlags, callback, state);
|
||||
}
|
||||
|
||||
public int EndSend(IAsyncResult asyncResult)
|
||||
{
|
||||
return _remote.EndSend(asyncResult);
|
||||
}
|
||||
|
||||
public void BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
|
||||
object state)
|
||||
{
|
||||
_remote.BeginReceive(buffer, offset, size, socketFlags, callback, state);
|
||||
}
|
||||
|
||||
public int EndReceive(IAsyncResult asyncResult)
|
||||
{
|
||||
return _remote.EndReceive(asyncResult);
|
||||
}
|
||||
|
||||
public void Shutdown(SocketShutdown how)
|
||||
{
|
||||
_remote.Shutdown(how);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_remote.Dispose();
|
||||
}
|
||||
|
||||
private void HttpRequestSendCallback(IAsyncResult ar)
|
||||
{
|
||||
var state = (HttpState) ar.AsyncState;
|
||||
try
|
||||
{
|
||||
_remote.EndSend(ar);
|
||||
|
||||
// start line read
|
||||
new LineReader(_remote, OnLineRead, OnException, OnFinish, Encoding.UTF8, HTTP_CRLF, 1024, new FakeAsyncResult(ar, state));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
state.ex = ex;
|
||||
state.Callback?.Invoke(new FakeAsyncResult(ar, state));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFinish(byte[] lastBytes, int index, int length, object state)
|
||||
{
|
||||
var st = (FakeAsyncResult)state;
|
||||
|
||||
if (st.innerState.ex == null)
|
||||
{
|
||||
if (!_established)
|
||||
{
|
||||
st.innerState.ex = new Exception(I18N.GetString("Proxy request failed"));
|
||||
}
|
||||
// TODO: save last bytes
|
||||
}
|
||||
st.innerState.Callback?.Invoke(st);
|
||||
}
|
||||
|
||||
private void OnException(Exception ex, object state)
|
||||
{
|
||||
var st = (FakeAsyncResult) state;
|
||||
|
||||
st.innerState.ex = ex;
|
||||
}
|
||||
|
||||
private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$");
|
||||
private int _respondLineCount = 0;
|
||||
private bool _established = false;
|
||||
|
||||
private bool OnLineRead(string line, object state)
|
||||
{
|
||||
Logging.Debug(line);
|
||||
|
||||
if (_respondLineCount == 0)
|
||||
{
|
||||
var m = HttpRespondHeaderRegex.Match(line);
|
||||
if (m.Success)
|
||||
{
|
||||
var resultCode = m.Groups[2].Value;
|
||||
if ("200" != resultCode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
_established = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (line.IsNullOrEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_respondLineCount++;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Shadowsocks.ForwardProxy
|
||||
{
|
||||
|
||||
public interface IForwardProxy
|
||||
{
|
||||
EndPoint LocalEndPoint { get; }
|
||||
|
||||
EndPoint ProxyEndPoint { get; }
|
||||
|
||||
EndPoint DestEndPoint { get; }
|
||||
|
||||
void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state);
|
||||
|
||||
void EndConnectProxy(IAsyncResult asyncResult);
|
||||
|
||||
void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state);
|
||||
|
||||
void EndConnectDest(IAsyncResult asyncResult);
|
||||
|
||||
void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
|
||||
object state);
|
||||
|
||||
int EndSend(IAsyncResult asyncResult);
|
||||
|
||||
void BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
|
||||
object state);
|
||||
|
||||
int EndReceive(IAsyncResult asyncResult);
|
||||
|
||||
void Shutdown(SocketShutdown how);
|
||||
|
||||
void Close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Shadowsocks.Controller;
|
||||
using Shadowsocks.Util.Sockets;
|
||||
|
||||
namespace Shadowsocks.ForwardProxy
|
||||
{
|
||||
public class Socks5Proxy : IForwardProxy
|
||||
{
|
||||
private class FakeAsyncResult : IAsyncResult
|
||||
{
|
||||
public readonly Socks5State innerState;
|
||||
|
||||
private readonly IAsyncResult r;
|
||||
|
||||
public FakeAsyncResult(IAsyncResult orig, Socks5State state)
|
||||
{
|
||||
r = orig;
|
||||
innerState = state;
|
||||
}
|
||||
|
||||
public bool IsCompleted => r.IsCompleted;
|
||||
public WaitHandle AsyncWaitHandle => r.AsyncWaitHandle;
|
||||
public object AsyncState => innerState.AsyncState;
|
||||
public bool CompletedSynchronously => r.CompletedSynchronously;
|
||||
}
|
||||
|
||||
private class Socks5State
|
||||
{
|
||||
public AsyncCallback Callback { get; set; }
|
||||
|
||||
public object AsyncState { get; set; }
|
||||
|
||||
public int BytesToRead;
|
||||
|
||||
public Exception ex { get; set; }
|
||||
}
|
||||
|
||||
private readonly WrappedSocket _remote = new WrappedSocket();
|
||||
|
||||
private const int Socks5PktMaxSize = 4 + 16 + 2;
|
||||
private readonly byte[] _receiveBuffer = new byte[Socks5PktMaxSize];
|
||||
|
||||
public EndPoint LocalEndPoint => _remote.LocalEndPoint;
|
||||
public EndPoint ProxyEndPoint { get; private set; }
|
||||
public EndPoint DestEndPoint { get; private set; }
|
||||
|
||||
public void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state)
|
||||
{
|
||||
var st = new Socks5State();
|
||||
st.Callback = callback;
|
||||
st.AsyncState = state;
|
||||
|
||||
ProxyEndPoint = remoteEP;
|
||||
|
||||
_remote.BeginConnect(remoteEP, ConnectCallback, st);
|
||||
}
|
||||
|
||||
public void EndConnectProxy(IAsyncResult asyncResult)
|
||||
{
|
||||
var state = ((FakeAsyncResult)asyncResult).innerState;
|
||||
|
||||
if (state.ex != null)
|
||||
{
|
||||
throw state.ex;
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state)
|
||||
{
|
||||
DestEndPoint = destEndPoint;
|
||||
|
||||
byte[] request = null;
|
||||
byte atyp = 0;
|
||||
int port;
|
||||
|
||||
var dep = destEndPoint as DnsEndPoint;
|
||||
if (dep != null)
|
||||
{
|
||||
// is a domain name, we will leave it to server
|
||||
|
||||
atyp = 3; // DOMAINNAME
|
||||
var enc = Encoding.UTF8;
|
||||
var hostByteCount = enc.GetByteCount(dep.Host);
|
||||
|
||||
request = new byte[4 + 1/*length byte*/ + hostByteCount + 2];
|
||||
request[4] = (byte)hostByteCount;
|
||||
enc.GetBytes(dep.Host, 0, dep.Host.Length, request, 5);
|
||||
|
||||
port = dep.Port;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (DestEndPoint.AddressFamily)
|
||||
{
|
||||
case AddressFamily.InterNetwork:
|
||||
request = new byte[4 + 4 + 2];
|
||||
atyp = 1; // IP V4 address
|
||||
break;
|
||||
case AddressFamily.InterNetworkV6:
|
||||
request = new byte[4 + 16 + 2];
|
||||
atyp = 4; // IP V6 address
|
||||
break;
|
||||
default:
|
||||
throw new Exception(I18N.GetString("Proxy request failed"));
|
||||
}
|
||||
port = ((IPEndPoint) DestEndPoint).Port;
|
||||
var addr = ((IPEndPoint)DestEndPoint).Address.GetAddressBytes();
|
||||
Array.Copy(addr, 0, request, 4, request.Length - 4 - 2);
|
||||
}
|
||||
|
||||
// 构造request包剩余部分
|
||||
request[0] = 5;
|
||||
request[1] = 1;
|
||||
request[2] = 0;
|
||||
request[3] = atyp;
|
||||
request[request.Length - 2] = (byte) ((port >> 8) & 0xff);
|
||||
request[request.Length - 1] = (byte) (port & 0xff);
|
||||
|
||||
var st = new Socks5State();
|
||||
st.Callback = callback;
|
||||
st.AsyncState = state;
|
||||
|
||||
_remote.BeginSend(request, 0, request.Length, 0, Socks5RequestSendCallback, st);
|
||||
}
|
||||
|
||||
public void EndConnectDest(IAsyncResult asyncResult)
|
||||
{
|
||||
var state = ((FakeAsyncResult)asyncResult).innerState;
|
||||
|
||||
if (state.ex != null)
|
||||
{
|
||||
throw state.ex;
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
|
||||
object state)
|
||||
{
|
||||
_remote.BeginSend(buffer, offset, size, socketFlags, callback, state);
|
||||
}
|
||||
|
||||
public int EndSend(IAsyncResult asyncResult)
|
||||
{
|
||||
return _remote.EndSend(asyncResult);
|
||||
}
|
||||
|
||||
public void BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
|
||||
object state)
|
||||
{
|
||||
_remote.BeginReceive(buffer, offset, size, socketFlags, callback, state);
|
||||
}
|
||||
|
||||
public int EndReceive(IAsyncResult asyncResult)
|
||||
{
|
||||
return _remote.EndReceive(asyncResult);
|
||||
}
|
||||
|
||||
public void Shutdown(SocketShutdown how)
|
||||
{
|
||||
_remote.Shutdown(how);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_remote.Dispose();
|
||||
}
|
||||
|
||||
|
||||
private void ConnectCallback(IAsyncResult ar)
|
||||
{
|
||||
var state = (Socks5State) ar.AsyncState;
|
||||
try
|
||||
{
|
||||
_remote.EndConnect(ar);
|
||||
|
||||
_remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
|
||||
|
||||
byte[] handshake = {5, 1, 0};
|
||||
_remote.BeginSend(handshake, 0, handshake.Length, 0, Socks5HandshakeSendCallback, state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
state.ex = ex;
|
||||
state.Callback?.Invoke(new FakeAsyncResult(ar, state));
|
||||
}
|
||||
}
|
||||
|
||||
private void Socks5HandshakeSendCallback(IAsyncResult ar)
|
||||
{
|
||||
var state = (Socks5State)ar.AsyncState;
|
||||
try
|
||||
{
|
||||
_remote.EndSend(ar);
|
||||
|
||||
_remote.BeginReceive(_receiveBuffer, 0, 2, 0, Socks5HandshakeReceiveCallback, state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
state.ex = ex;
|
||||
state.Callback?.Invoke(new FakeAsyncResult(ar, state));
|
||||
}
|
||||
}
|
||||
|
||||
private void Socks5HandshakeReceiveCallback(IAsyncResult ar)
|
||||
{
|
||||
Exception ex = null;
|
||||
var state = (Socks5State)ar.AsyncState;
|
||||
try
|
||||
{
|
||||
var bytesRead = _remote.EndReceive(ar);
|
||||
if (bytesRead >= 2)
|
||||
{
|
||||
if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0)
|
||||
{
|
||||
ex = new Exception(I18N.GetString("Proxy handshake failed"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ex = new Exception(I18N.GetString("Proxy handshake failed"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
ex = ex2;
|
||||
}
|
||||
state.ex = ex;
|
||||
state.Callback?.Invoke(new FakeAsyncResult(ar, state));
|
||||
}
|
||||
|
||||
|
||||
private void Socks5RequestSendCallback(IAsyncResult ar)
|
||||
{
|
||||
var state = (Socks5State)ar.AsyncState;
|
||||
try
|
||||
{
|
||||
_remote.EndSend(ar);
|
||||
|
||||
_remote.BeginReceive(_receiveBuffer, 0, 4, 0, Socks5ReplyReceiveCallback, state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
state.ex = ex;
|
||||
state.Callback?.Invoke(new FakeAsyncResult(ar, state));
|
||||
}
|
||||
}
|
||||
|
||||
private void Socks5ReplyReceiveCallback(IAsyncResult ar)
|
||||
{
|
||||
var state = (Socks5State)ar.AsyncState;
|
||||
try
|
||||
{
|
||||
var bytesRead = _remote.EndReceive(ar);
|
||||
if (bytesRead >= 4)
|
||||
{
|
||||
if (_receiveBuffer[0] == 5 && _receiveBuffer[1] == 0)
|
||||
{
|
||||
// 跳过剩下的reply
|
||||
switch (_receiveBuffer[3]) // atyp
|
||||
{
|
||||
case 1:
|
||||
state.BytesToRead = 4 + 2;
|
||||
_remote.BeginReceive(_receiveBuffer, 0, 4 + 2, 0, Socks5ReplyReceiveCallback2, state);
|
||||
break;
|
||||
case 4:
|
||||
state.BytesToRead = 16 + 2;
|
||||
_remote.BeginReceive(_receiveBuffer, 0, 16 + 2, 0, Socks5ReplyReceiveCallback2, state);
|
||||
break;
|
||||
default:
|
||||
state.ex = new Exception(I18N.GetString("Proxy request failed"));
|
||||
state.Callback?.Invoke(new FakeAsyncResult(ar, state));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state.ex = new Exception(I18N.GetString("Proxy request failed"));
|
||||
state.Callback?.Invoke(new FakeAsyncResult(ar, state));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state.ex = new Exception(I18N.GetString("Proxy request failed"));
|
||||
state.Callback?.Invoke(new FakeAsyncResult(ar, state));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
state.ex = ex;
|
||||
state.Callback?.Invoke(new FakeAsyncResult(ar, state));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Socks5ReplyReceiveCallback2(IAsyncResult ar)
|
||||
{
|
||||
Exception ex = null;
|
||||
var state = (Socks5State)ar.AsyncState;
|
||||
try
|
||||
{
|
||||
var bytesRead = _remote.EndReceive(ar);
|
||||
var bytesNeedSkip = state.BytesToRead;
|
||||
|
||||
if (bytesRead < bytesNeedSkip)
|
||||
{
|
||||
ex = new Exception(I18N.GetString("Proxy request failed"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
ex = ex2;
|
||||
}
|
||||
|
||||
state.ex = ex;
|
||||
state.Callback?.Invoke(new FakeAsyncResult(ar, state));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user