@@ -43,6 +43,9 @@ namespace Shadowsocks.Crypto.AEAD | |||||
// Is first chunk(tcp request) | // Is first chunk(tcp request) | ||||
protected bool tcpRequestSent; | protected bool tcpRequestSent; | ||||
// [len(2)][lentag][data][datatag] | |||||
private int ChunkOverhead => tagLen * 2 + 2; | |||||
public AEADCrypto(string method, string password) | public AEADCrypto(string method, string password) | ||||
: base(method, password) | : base(method, password) | ||||
{ | { | ||||
@@ -119,17 +122,6 @@ namespace Shadowsocks.Crypto.AEAD | |||||
outlength = saltLen; | outlength = saltLen; | ||||
} | } | ||||
if (!tcpRequestSent) | |||||
{ | |||||
tcpRequestSent = true; | |||||
// read addr byte to encrypt | |||||
int encAddrBufLength = ChunkEncrypt(tmp.Slice(0, AddressBufferLength), cipher.Slice(outlength)); | |||||
tmp = tmp.Slice(AddressBufferLength); | |||||
outlength += encAddrBufLength; | |||||
} | |||||
// handle other chunks | |||||
while (true) | while (true) | ||||
{ | { | ||||
// calculate next chunk size | // calculate next chunk size | ||||
@@ -147,7 +139,7 @@ namespace Shadowsocks.Crypto.AEAD | |||||
// check if we have enough space for outbuf | // check if we have enough space for outbuf | ||||
// if not, keep buf for next run, at this condition, buffer is not empty | // if not, keep buf for next run, at this condition, buffer is not empty | ||||
if (outlength + TCPParameter.ChunkOverheadSize > TCPParameter.BufferSize) | |||||
if (outlength + ChunkOverhead > cipher.Length) | |||||
{ | { | ||||
logger.Debug("enc outbuf almost full, giving up"); | logger.Debug("enc outbuf almost full, giving up"); | ||||
@@ -234,7 +226,7 @@ namespace Shadowsocks.Crypto.AEAD | |||||
outlength += len; | outlength += len; | ||||
// logger.Debug("aead dec outlength " + outlength); | // logger.Debug("aead dec outlength " + outlength); | ||||
if (outlength + 100 > TCPParameter.BufferSize) | |||||
if (outlength + ChunkOverhead > cipher.Length) | |||||
{ | { | ||||
logger.Trace($"{instanceId} output almost full, write {tmp.Length} byte back to buffer."); | logger.Trace($"{instanceId} output almost full, write {tmp.Length} byte back to buffer."); | ||||
tmp.CopyTo(buffer); | tmp.CopyTo(buffer); | ||||
@@ -62,12 +62,12 @@ namespace Shadowsocks.Crypto | |||||
_registeredEncryptors.Add(method.Key, typeof(AEADAesGcmNativeCrypto)); | _registeredEncryptors.Add(method.Key, typeof(AEADAesGcmNativeCrypto)); | ||||
} | } | ||||
} | } | ||||
foreach (var method in AEADNaClCrypto.SupportedCiphers()) | |||||
foreach (var method in AEADBouncyCastleCrypto.SupportedCiphers()) | |||||
{ | { | ||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | if (!_registeredEncryptors.ContainsKey(method.Key)) | ||||
{ | { | ||||
ciphers.Add(method.Key, method.Value); | ciphers.Add(method.Key, method.Value); | ||||
_registeredEncryptors.Add(method.Key, typeof(AEADNaClCrypto)); | |||||
_registeredEncryptors.Add(method.Key, typeof(AEADBouncyCastleCrypto)); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -4,8 +4,6 @@ namespace Shadowsocks.Crypto | |||||
{ | { | ||||
public interface ICrypto | public interface ICrypto | ||||
{ | { | ||||
/* length == -1 means not used */ | |||||
int AddressBufferLength { set; get; } | |||||
int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher); | int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher); | ||||
int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher); | int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher); | ||||
int EncryptUDP(ReadOnlySpan<byte> plain, Span<byte> cipher); | int EncryptUDP(ReadOnlySpan<byte> plain, Span<byte> cipher); | ||||
@@ -46,7 +46,7 @@ namespace Shadowsocks.Controller | |||||
private byte[] _firstPacket; | private byte[] _firstPacket; | ||||
private int _firstPacketLength; | private int _firstPacketLength; | ||||
private Socket _local; | private Socket _local; | ||||
private WrappedSocket _remote; | |||||
private Socket _remote; | |||||
private bool _closed = false; | private bool _closed = false; | ||||
private bool _localShutdown = false; | private bool _localShutdown = false; | ||||
private bool _remoteShutdown = false; | private bool _remoteShutdown = false; | ||||
@@ -70,7 +70,7 @@ namespace Shadowsocks.Controller | |||||
EndPoint remoteEP = SocketUtil.GetEndPoint(_local.AddressFamily == AddressFamily.InterNetworkV6 ? "[::1]" : "127.0.0.1", targetPort); | EndPoint remoteEP = SocketUtil.GetEndPoint(_local.AddressFamily == AddressFamily.InterNetworkV6 ? "[::1]" : "127.0.0.1", targetPort); | ||||
// Connect to the remote endpoint. | // Connect to the remote endpoint. | ||||
_remote = new WrappedSocket(); | |||||
_remote = new Socket(SocketType.Stream, ProtocolType.Tcp); | |||||
_remote.BeginConnect(remoteEP, ConnectCallback, null); | _remote.BeginConnect(remoteEP, ConnectCallback, null); | ||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
@@ -2,6 +2,7 @@ | |||||
using System.Net; | using System.Net; | ||||
using System.Net.Sockets; | using System.Net.Sockets; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | |||||
using Shadowsocks.Util.Sockets; | using Shadowsocks.Util.Sockets; | ||||
namespace Shadowsocks.Proxy | namespace Shadowsocks.Proxy | ||||
@@ -31,69 +32,42 @@ namespace Shadowsocks.Proxy | |||||
} | } | ||||
} | } | ||||
private WrappedSocket _remote = new WrappedSocket(); | |||||
private readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp); | |||||
public EndPoint LocalEndPoint => _remote.LocalEndPoint; | public EndPoint LocalEndPoint => _remote.LocalEndPoint; | ||||
public EndPoint ProxyEndPoint { get; } = new FakeEndPoint(); | public EndPoint ProxyEndPoint { get; } = new FakeEndPoint(); | ||||
public EndPoint DestEndPoint { get; private set; } | 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, NetworkCredential auth = null) | |||||
{ | |||||
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) | |||||
public void Shutdown(SocketShutdown how) | |||||
{ | { | ||||
_remote.BeginSend(buffer, offset, size, socketFlags, callback, state); | |||||
_remote.Shutdown(how); | |||||
} | } | ||||
public int EndSend(IAsyncResult asyncResult) | |||||
public void Close() | |||||
{ | { | ||||
return _remote.EndSend(asyncResult); | |||||
_remote.Dispose(); | |||||
} | } | ||||
public void BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, | |||||
object state) | |||||
public Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default) | |||||
{ | { | ||||
_remote.BeginReceive(buffer, offset, size, socketFlags, callback, state); | |||||
return Task.CompletedTask; | |||||
} | } | ||||
public int EndReceive(IAsyncResult asyncResult) | |||||
public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default) | |||||
{ | { | ||||
return _remote.EndReceive(asyncResult); | |||||
DestEndPoint = destEndPoint; | |||||
await _remote.ConnectAsync(destEndPoint); | |||||
} | } | ||||
public void Shutdown(SocketShutdown how) | |||||
public async Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default) | |||||
{ | { | ||||
_remote.Shutdown(how); | |||||
return await _remote.SendAsync(buffer, SocketFlags.None, token); | |||||
} | } | ||||
public void Close() | |||||
public async Task<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default) | |||||
{ | { | ||||
_remote.Dispose(); | |||||
return await _remote.ReceiveAsync(buffer, SocketFlags.None, token); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -4,6 +4,7 @@ using System.Net.Sockets; | |||||
using System.Text; | using System.Text; | ||||
using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | |||||
using NLog; | using NLog; | ||||
using Shadowsocks.Controller; | using Shadowsocks.Controller; | ||||
using Shadowsocks.Util.Sockets; | using Shadowsocks.Util.Sockets; | ||||
@@ -12,60 +13,17 @@ namespace Shadowsocks.Proxy | |||||
{ | { | ||||
public class HttpProxy : IProxy | public class HttpProxy : IProxy | ||||
{ | { | ||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
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 Exception ex { get; set; } | |||||
} | |||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); | |||||
public EndPoint LocalEndPoint => _remote.LocalEndPoint; | public EndPoint LocalEndPoint => _remote.LocalEndPoint; | ||||
public EndPoint ProxyEndPoint { get; private set; } | public EndPoint ProxyEndPoint { get; private set; } | ||||
public EndPoint DestEndPoint { 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 readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp); | |||||
private const string HTTP_CRLF = "\r\n"; | private const string HTTP_CRLF = "\r\n"; | ||||
private const string HTTP_CONNECT_TEMPLATE = | |||||
"CONNECT {0} HTTP/1.1" + HTTP_CRLF + | |||||
private const string HTTP_CONNECT_TEMPLATE = | |||||
"CONNECT {0} HTTP/1.1" + HTTP_CRLF + | |||||
"Host: {0}" + HTTP_CRLF + | "Host: {0}" + HTTP_CRLF + | ||||
"Proxy-Connection: keep-alive" + 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/85.0.4183.83 Safari/537.36" + HTTP_CRLF + | "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" + HTTP_CRLF + | ||||
@@ -73,58 +31,6 @@ namespace Shadowsocks.Proxy | |||||
"" + HTTP_CRLF; // End with an empty line | "" + HTTP_CRLF; // End with an empty line | ||||
private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF; | private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF; | ||||
public void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state, NetworkCredential auth = null) | |||||
{ | |||||
DestEndPoint = destEndPoint; | |||||
String authInfo = ""; | |||||
if (auth != null) | |||||
{ | |||||
string authKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth.UserName + ":" + auth.Password)); | |||||
authInfo = string.Format(PROXY_AUTH_TEMPLATE, authKey); | |||||
} | |||||
string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo); | |||||
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) | public void Shutdown(SocketShutdown how) | ||||
{ | { | ||||
_remote.Shutdown(how); | _remote.Shutdown(how); | ||||
@@ -135,45 +41,6 @@ namespace Shadowsocks.Proxy | |||||
_remote.Dispose(); | _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}) (.+)$", RegexOptions.Compiled); | private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$", RegexOptions.Compiled); | ||||
private int _respondLineCount = 0; | private int _respondLineCount = 0; | ||||
private bool _established = false; | private bool _established = false; | ||||
@@ -206,5 +73,45 @@ namespace Shadowsocks.Proxy | |||||
return false; | return false; | ||||
} | } | ||||
private NetworkCredential auth; | |||||
public async Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default) | |||||
{ | |||||
ProxyEndPoint = remoteEP; | |||||
this.auth = auth; | |||||
await _remote.ConnectAsync(remoteEP); | |||||
_remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||||
} | |||||
public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default) | |||||
{ | |||||
DestEndPoint = destEndPoint; | |||||
String authInfo = ""; | |||||
if (auth != null) | |||||
{ | |||||
string authKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth.UserName + ":" + auth.Password)); | |||||
authInfo = string.Format(PROXY_AUTH_TEMPLATE, authKey); | |||||
} | |||||
string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo); | |||||
var b = Encoding.UTF8.GetBytes(request); | |||||
await _remote.SendAsync(Encoding.UTF8.GetBytes(request), SocketFlags.None, token); | |||||
// start line read | |||||
LineReader reader = new LineReader(_remote, OnLineRead, (e, _) => throw e, (_1, _2, _3, _4) => { }, Encoding.UTF8, HTTP_CRLF, 1024, null); | |||||
await reader.Finished; | |||||
} | |||||
public async Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default) | |||||
{ | |||||
return await _remote.SendAsync(buffer, SocketFlags.None, token); | |||||
} | |||||
public async Task<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default) | |||||
{ | |||||
return await _remote.ReceiveAsync(buffer, SocketFlags.None, token); | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,6 +1,8 @@ | |||||
using System; | using System; | ||||
using System.Net; | using System.Net; | ||||
using System.Net.Sockets; | using System.Net.Sockets; | ||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace Shadowsocks.Proxy | namespace Shadowsocks.Proxy | ||||
{ | { | ||||
@@ -13,23 +15,13 @@ namespace Shadowsocks.Proxy | |||||
EndPoint DestEndPoint { get; } | EndPoint DestEndPoint { get; } | ||||
void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state); | |||||
Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default); | |||||
void EndConnectProxy(IAsyncResult asyncResult); | |||||
Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default); | |||||
void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state, NetworkCredential auth = null); | |||||
Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default); | |||||
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); | |||||
Task<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default); | |||||
void Shutdown(SocketShutdown how); | void Shutdown(SocketShutdown how); | ||||
@@ -3,6 +3,7 @@ using System.Net; | |||||
using System.Net.Sockets; | using System.Net.Sockets; | ||||
using System.Text; | using System.Text; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | |||||
using Shadowsocks.Controller; | using Shadowsocks.Controller; | ||||
using Shadowsocks.Util.Sockets; | using Shadowsocks.Util.Sockets; | ||||
@@ -10,36 +11,7 @@ namespace Shadowsocks.Proxy | |||||
{ | { | ||||
public class Socks5Proxy : IProxy | public class Socks5Proxy : IProxy | ||||
{ | { | ||||
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 readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp); | |||||
private const int Socks5PktMaxSize = 4 + 16 + 2; | private const int Socks5PktMaxSize = 4 + 16 + 2; | ||||
private readonly byte[] _receiveBuffer = new byte[Socks5PktMaxSize]; | private readonly byte[] _receiveBuffer = new byte[Socks5PktMaxSize]; | ||||
@@ -48,38 +20,40 @@ namespace Shadowsocks.Proxy | |||||
public EndPoint ProxyEndPoint { get; private set; } | public EndPoint ProxyEndPoint { get; private set; } | ||||
public EndPoint DestEndPoint { get; private set; } | public EndPoint DestEndPoint { get; private set; } | ||||
public void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state) | |||||
public void Shutdown(SocketShutdown how) | |||||
{ | { | ||||
var st = new Socks5State(); | |||||
st.Callback = callback; | |||||
st.AsyncState = state; | |||||
ProxyEndPoint = remoteEP; | |||||
_remote.BeginConnect(remoteEP, ConnectCallback, st); | |||||
_remote.Shutdown(how); | |||||
} | } | ||||
public void EndConnectProxy(IAsyncResult asyncResult) | |||||
public void Close() | |||||
{ | { | ||||
var state = ((FakeAsyncResult)asyncResult).innerState; | |||||
_remote.Dispose(); | |||||
} | |||||
if (state.ex != null) | |||||
public async Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default) | |||||
{ | |||||
ProxyEndPoint = remoteEP; | |||||
await _remote.ConnectAsync(remoteEP); | |||||
await _remote.SendAsync(new byte[] { 5, 1, 0 }, SocketFlags.None); | |||||
if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, 2), SocketFlags.None) != 2) | |||||
{ | |||||
throw new Exception(I18N.GetString("Proxy handshake failed")); | |||||
} | |||||
if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0) | |||||
{ | { | ||||
throw state.ex; | |||||
throw new Exception(I18N.GetString("Proxy handshake failed")); | |||||
} | } | ||||
} | } | ||||
public void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state, NetworkCredential auth = null) | |||||
public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default) | |||||
{ | { | ||||
// TODO: support SOCKS5 auth | // TODO: support SOCKS5 auth | ||||
DestEndPoint = destEndPoint; | DestEndPoint = destEndPoint; | ||||
byte[] request = null; | |||||
byte atyp = 0; | |||||
byte[] request; | |||||
byte atyp; | |||||
int port; | int port; | ||||
var dep = destEndPoint as DnsEndPoint; | |||||
if (dep != null) | |||||
if (destEndPoint is DnsEndPoint dep) | |||||
{ | { | ||||
// is a domain name, we will leave it to server | // is a domain name, we will leave it to server | ||||
@@ -108,7 +82,7 @@ namespace Shadowsocks.Proxy | |||||
default: | default: | ||||
throw new Exception(I18N.GetString("Proxy request failed")); | throw new Exception(I18N.GetString("Proxy request failed")); | ||||
} | } | ||||
port = ((IPEndPoint) DestEndPoint).Port; | |||||
port = ((IPEndPoint)DestEndPoint).Port; | |||||
var addr = ((IPEndPoint)DestEndPoint).Address.GetAddressBytes(); | var addr = ((IPEndPoint)DestEndPoint).Address.GetAddressBytes(); | ||||
Array.Copy(addr, 0, request, 4, request.Length - 4 - 2); | Array.Copy(addr, 0, request, 4, request.Length - 4 - 2); | ||||
} | } | ||||
@@ -118,206 +92,42 @@ namespace Shadowsocks.Proxy | |||||
request[1] = 1; | request[1] = 1; | ||||
request[2] = 0; | request[2] = 0; | ||||
request[3] = atyp; | 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); | |||||
} | |||||
request[^2] = (byte)((port >> 8) & 0xff); | |||||
request[^1] = (byte)(port & 0xff); | |||||
public void Close() | |||||
{ | |||||
_remote.Dispose(); | |||||
} | |||||
await _remote.SendAsync(request, SocketFlags.None, token); | |||||
private void ConnectCallback(IAsyncResult ar) | |||||
{ | |||||
var state = (Socks5State) ar.AsyncState; | |||||
try | |||||
if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, 4), SocketFlags.None, token) != 4) | |||||
{ | { | ||||
_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) | |||||
throw new Exception(I18N.GetString("Proxy request failed")); | |||||
}; | |||||
if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0) | |||||
{ | { | ||||
state.ex = ex; | |||||
state.Callback?.Invoke(new FakeAsyncResult(ar, state)); | |||||
throw new Exception(I18N.GetString("Proxy request failed")); | |||||
} | } | ||||
} | |||||
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 addrLen = _receiveBuffer[3] switch | |||||
{ | { | ||||
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) | |||||
1 => 6, | |||||
4 => 18, | |||||
_ => throw new NotImplementedException(), | |||||
}; | |||||
if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, addrLen), SocketFlags.None, token) != addrLen) | |||||
{ | { | ||||
ex = ex2; | |||||
throw new Exception(I18N.GetString("Proxy request failed")); | |||||
} | } | ||||
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) | |||||
public async Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default) | |||||
{ | { | ||||
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)); | |||||
} | |||||
return await _remote.SendAsync(buffer, SocketFlags.None, token); | |||||
} | } | ||||
private void Socks5ReplyReceiveCallback2(IAsyncResult ar) | |||||
public async Task<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default) | |||||
{ | { | ||||
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)); | |||||
return await _remote.ReceiveAsync(buffer, SocketFlags.None, token); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -1,11 +1,13 @@ | |||||
using System; | using System; | ||||
using System.Net.Sockets; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | |||||
namespace Shadowsocks.Util.Sockets | namespace Shadowsocks.Util.Sockets | ||||
{ | { | ||||
public class LineReader | public class LineReader | ||||
{ | { | ||||
private readonly WrappedSocket _socket; | |||||
private readonly Socket _socket; | |||||
private readonly Func<string, object, bool> _onLineRead; | private readonly Func<string, object, bool> _onLineRead; | ||||
private readonly Action<Exception, object> _onException; | private readonly Action<Exception, object> _onException; | ||||
private readonly Action<byte[], int, int, object> _onFinish; | private readonly Action<byte[], int, int, object> _onFinish; | ||||
@@ -20,7 +22,10 @@ namespace Shadowsocks.Util.Sockets | |||||
private int _bufferIndex; | private int _bufferIndex; | ||||
public LineReader(WrappedSocket socket, Func<string, object, bool> onLineRead, Action<Exception, object> onException, | |||||
private readonly TaskCompletionSource<int> finishPromise = new TaskCompletionSource<int>(); | |||||
public Task Finished => finishPromise.Task; | |||||
public LineReader(Socket socket, Func<string, object, bool> onLineRead, Action<Exception, object> onException, | |||||
Action<byte[], int, int, object> onFinish, Encoding encoding, string delimiter, int maxLineBytes, object state) | Action<byte[], int, int, object> onFinish, Encoding encoding, string delimiter, int maxLineBytes, object state) | ||||
{ | { | ||||
if (socket == null) | if (socket == null) | ||||
@@ -80,6 +85,7 @@ namespace Shadowsocks.Util.Sockets | |||||
if (bytesRead == 0) | if (bytesRead == 0) | ||||
{ | { | ||||
OnFinish(length); | OnFinish(length); | ||||
finishPromise.TrySetResult(0); | |||||
return; | return; | ||||
} | } | ||||
@@ -128,6 +134,7 @@ namespace Shadowsocks.Util.Sockets | |||||
private void OnException(Exception ex) | private void OnException(Exception ex) | ||||
{ | { | ||||
finishPromise.TrySetException(ex); | |||||
_onException?.Invoke(ex, _state); | _onException?.Invoke(ex, _state); | ||||
} | } | ||||
@@ -1,268 +0,0 @@ | |||||
using System; | |||||
using System.Net; | |||||
using System.Net.Sockets; | |||||
using System.Threading; | |||||
namespace Shadowsocks.Util.Sockets | |||||
{ | |||||
/* | |||||
* A wrapped socket class which support both ipv4 and ipv6 based on the | |||||
* connected remote endpoint. | |||||
* | |||||
* If the server address is host name, then it may have both ipv4 and ipv6 address | |||||
* after resolving. The main idea is we don't want to resolve and choose the address | |||||
* by ourself. Instead, Socket.ConnectAsync() do handle this thing internally by trying | |||||
* each address and returning an established socket connection. | |||||
*/ | |||||
public class WrappedSocket | |||||
{ | |||||
public EndPoint LocalEndPoint => _activeSocket?.LocalEndPoint; | |||||
// Only used during connection and close, so it won't cost too much. | |||||
private SpinLock _socketSyncLock = new SpinLock(); | |||||
private bool _disposed; | |||||
private bool Connected => _activeSocket != null; | |||||
private Socket _activeSocket; | |||||
public void BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state) | |||||
{ | |||||
if (_disposed) | |||||
{ | |||||
throw new ObjectDisposedException(GetType().FullName); | |||||
} | |||||
if (Connected) | |||||
{ | |||||
throw new SocketException((int) SocketError.IsConnected); | |||||
} | |||||
var arg = new SocketAsyncEventArgs(); | |||||
arg.RemoteEndPoint = remoteEP; | |||||
arg.Completed += OnTcpConnectCompleted; | |||||
arg.UserToken = new TcpUserToken(callback, state); | |||||
if(!Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, arg)) | |||||
{ | |||||
OnTcpConnectCompleted(this, arg); | |||||
} | |||||
} | |||||
private class FakeAsyncResult : IAsyncResult | |||||
{ | |||||
public bool IsCompleted { get; } = true; | |||||
public WaitHandle AsyncWaitHandle { get; } = null; | |||||
public object AsyncState { get; set; } | |||||
public bool CompletedSynchronously { get; } = true; | |||||
public Exception InternalException { get; set; } = null; | |||||
} | |||||
private class TcpUserToken | |||||
{ | |||||
public AsyncCallback Callback { get; } | |||||
public object AsyncState { get; } | |||||
public TcpUserToken(AsyncCallback callback, object state) | |||||
{ | |||||
Callback = callback; | |||||
AsyncState = state; | |||||
} | |||||
} | |||||
private void OnTcpConnectCompleted(object sender, SocketAsyncEventArgs args) | |||||
{ | |||||
using (args) | |||||
{ | |||||
args.Completed -= OnTcpConnectCompleted; | |||||
var token = (TcpUserToken) args.UserToken; | |||||
if (args.SocketError != SocketError.Success) | |||||
{ | |||||
var ex = args.ConnectByNameError ?? new SocketException((int) args.SocketError); | |||||
var r = new FakeAsyncResult() | |||||
{ | |||||
AsyncState = token.AsyncState, | |||||
InternalException = ex | |||||
}; | |||||
token.Callback(r); | |||||
} | |||||
else | |||||
{ | |||||
var lockTaken = false; | |||||
if (!_socketSyncLock.IsHeldByCurrentThread) | |||||
{ | |||||
_socketSyncLock.TryEnter(ref lockTaken); | |||||
} | |||||
try | |||||
{ | |||||
if (Connected) | |||||
{ | |||||
args.ConnectSocket.FullClose(); | |||||
} | |||||
else | |||||
{ | |||||
_activeSocket = args.ConnectSocket; | |||||
if (_disposed) | |||||
{ | |||||
_activeSocket.FullClose(); | |||||
} | |||||
var r = new FakeAsyncResult() | |||||
{ | |||||
AsyncState = token.AsyncState | |||||
}; | |||||
token.Callback(r); | |||||
} | |||||
} | |||||
finally | |||||
{ | |||||
if (lockTaken) | |||||
{ | |||||
_socketSyncLock.Exit(); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
public void EndConnect(IAsyncResult asyncResult) | |||||
{ | |||||
if (_disposed) | |||||
{ | |||||
throw new ObjectDisposedException(GetType().FullName); | |||||
} | |||||
var r = asyncResult as FakeAsyncResult; | |||||
if (r == null) | |||||
{ | |||||
throw new ArgumentException("Invalid asyncResult.", nameof(asyncResult)); | |||||
} | |||||
if (r.InternalException != null) | |||||
{ | |||||
throw r.InternalException; | |||||
} | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
if (_disposed) | |||||
{ | |||||
return; | |||||
} | |||||
var lockTaken = false; | |||||
if (!_socketSyncLock.IsHeldByCurrentThread) | |||||
{ | |||||
_socketSyncLock.TryEnter(ref lockTaken); | |||||
} | |||||
try | |||||
{ | |||||
_disposed = true; | |||||
_activeSocket?.FullClose(); | |||||
} | |||||
finally | |||||
{ | |||||
if (lockTaken) | |||||
{ | |||||
_socketSyncLock.Exit(); | |||||
} | |||||
} | |||||
} | |||||
public IAsyncResult BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, | |||||
AsyncCallback callback, | |||||
object state) | |||||
{ | |||||
if (_disposed) | |||||
{ | |||||
throw new ObjectDisposedException(GetType().FullName); | |||||
} | |||||
if (!Connected) | |||||
{ | |||||
throw new SocketException((int) SocketError.NotConnected); | |||||
} | |||||
return _activeSocket.BeginSend(buffer, offset, size, socketFlags, callback, state); | |||||
} | |||||
public int EndSend(IAsyncResult asyncResult) | |||||
{ | |||||
if (_disposed) | |||||
{ | |||||
throw new ObjectDisposedException(GetType().FullName); | |||||
} | |||||
if (!Connected) | |||||
{ | |||||
throw new SocketException((int) SocketError.NotConnected); | |||||
} | |||||
return _activeSocket.EndSend(asyncResult); | |||||
} | |||||
public IAsyncResult BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, | |||||
AsyncCallback callback, | |||||
object state) | |||||
{ | |||||
if (_disposed) | |||||
{ | |||||
throw new ObjectDisposedException(GetType().FullName); | |||||
} | |||||
if (!Connected) | |||||
{ | |||||
throw new SocketException((int) SocketError.NotConnected); | |||||
} | |||||
return _activeSocket.BeginReceive(buffer, offset, size, socketFlags, callback, state); | |||||
} | |||||
public int EndReceive(IAsyncResult asyncResult) | |||||
{ | |||||
if (_disposed) | |||||
{ | |||||
throw new ObjectDisposedException(GetType().FullName); | |||||
} | |||||
if (!Connected) | |||||
{ | |||||
throw new SocketException((int) SocketError.NotConnected); | |||||
} | |||||
return _activeSocket.EndReceive(asyncResult); | |||||
} | |||||
public void Shutdown(SocketShutdown how) | |||||
{ | |||||
if (_disposed) | |||||
{ | |||||
throw new ObjectDisposedException(GetType().FullName); | |||||
} | |||||
if (!Connected) | |||||
{ | |||||
return; | |||||
} | |||||
_activeSocket.Shutdown(how); | |||||
} | |||||
public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue) | |||||
{ | |||||
SetSocketOption(optionLevel, optionName, optionValue ? 1 : 0); | |||||
} | |||||
public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue) | |||||
{ | |||||
if (_disposed) | |||||
{ | |||||
throw new ObjectDisposedException(GetType().FullName); | |||||
} | |||||
if (!Connected) | |||||
{ | |||||
throw new SocketException((int)SocketError.NotConnected); | |||||
} | |||||
_activeSocket.SetSocketOption(optionLevel, optionName, optionValue); | |||||
} | |||||
} | |||||
} |
@@ -55,10 +55,7 @@ namespace Shadowsocks.Test | |||||
{ | { | ||||
IEncryptor encryptor = (IEncryptor)ector.Invoke(new object[] { method, password }); | IEncryptor encryptor = (IEncryptor)ector.Invoke(new object[] { method, password }); | ||||
IEncryptor decryptor = (IEncryptor)dctor.Invoke(new object[] { method, password }); | IEncryptor decryptor = (IEncryptor)dctor.Invoke(new object[] { method, password }); | ||||
encryptor.AddressBufferLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; | |||||
decryptor.AddressBufferLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; | |||||
for (int i = 0; i < 16; i++) | for (int i = 0; i < 16; i++) | ||||
{ | { | ||||
RunEncryptionRound(encryptor, decryptor); | RunEncryptionRound(encryptor, decryptor); | ||||