@@ -1,15 +1,26 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | |||||
using System.Net.Sockets; | using System.Net.Sockets; | ||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | |||||
using System.Text.RegularExpressions; | |||||
using Shadowsocks.ForwardProxy; | |||||
using Shadowsocks.Util.Sockets; | using Shadowsocks.Util.Sockets; | ||||
namespace Shadowsocks.Controller.Service | namespace Shadowsocks.Controller.Service | ||||
{ | { | ||||
class Http2Socks5 : Listener.Service | class Http2Socks5 : Listener.Service | ||||
{ | { | ||||
private readonly ByteSearch.SearchTarget _connectSearch = | |||||
new ByteSearch.SearchTarget(Encoding.UTF8.GetBytes("HTTP")); | |||||
private readonly int _socks5Port; | |||||
public Http2Socks5(int socks5Port) | |||||
{ | |||||
_socks5Port = socks5Port; | |||||
} | |||||
public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) | public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) | ||||
{ | { | ||||
if (socket.ProtocolType != ProtocolType.Tcp) | if (socket.ProtocolType != ProtocolType.Tcp) | ||||
@@ -17,7 +28,465 @@ namespace Shadowsocks.Controller.Service | |||||
return false; | return false; | ||||
} | } | ||||
return true; | |||||
if (_connectSearch.SearchIn(firstPacket, 0, length) != -1) | |||||
{ | |||||
new HttpHandler(_socks5Port, firstPacket, length, socket); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
private class HttpHandler | |||||
{ | |||||
private const string HTTP_CRLF = "\r\n"; | |||||
private const string HTTP_CONNECT_200 = | |||||
"HTTP/1.1 200 Connection established" + HTTP_CRLF + | |||||
"Proxy-Connection: close" + HTTP_CRLF + | |||||
"Proxy-Agent: Shadowsocks" + HTTP_CRLF + | |||||
"" + HTTP_CRLF; // End with an empty line | |||||
private readonly WrappedSocket _localSocket; | |||||
private readonly int _socks5Port; | |||||
private Socks5Proxy _socks5; | |||||
private bool _closed = false; | |||||
private bool _localShutdown = false; | |||||
private bool _remoteShutdown = false; | |||||
private readonly object _Lock = new object(); | |||||
private const int RecvSize = 16384; | |||||
// remote receive buffer | |||||
private readonly byte[] _remoteRecvBuffer = new byte[RecvSize]; | |||||
// connection receive buffer | |||||
private readonly byte[] _connetionRecvBuffer = new byte[RecvSize]; | |||||
public HttpHandler(int socks5Port, byte[] firstPacket, int length, Socket socket) | |||||
{ | |||||
_socks5Port = socks5Port; | |||||
_localSocket = new WrappedSocket(socket); | |||||
_localSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||||
new LineReader(_localSocket, firstPacket, 0, length, OnLineRead, OnException, OnFinish, | |||||
Encoding.UTF8, HTTP_CRLF, 1024, null); | |||||
} | |||||
private void CheckClose() | |||||
{ | |||||
if (_localShutdown && _remoteShutdown) | |||||
{ | |||||
Close(); | |||||
} | |||||
} | |||||
private void Close() | |||||
{ | |||||
lock (_Lock) | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return; | |||||
} | |||||
_closed = true; | |||||
} | |||||
_localSocket.Dispose(); | |||||
_socks5?.Close(); | |||||
} | |||||
private byte[] _lastBytes; | |||||
private int _lastBytesIndex; | |||||
private int _lastBytesLength; | |||||
#region Socks5 Process | |||||
private void ProxyConnectCallback(IAsyncResult ar) | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
_socks5.EndConnectProxy(ar); | |||||
_socks5.BeginConnectDest(SocketUtil.GetEndPoint(_targetHost, _targetPort), ConnectCallback, null); | |||||
} | |||||
catch (ArgumentException) | |||||
{ | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
Logging.LogUsefulException(e); | |||||
Close(); | |||||
} | |||||
} | |||||
private void ConnectCallback(IAsyncResult ar) | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
_socks5.EndConnectDest(ar); | |||||
if (_isConnect) | |||||
{ | |||||
// http connect response | |||||
SendConnectResponse(); | |||||
} | |||||
else | |||||
{ | |||||
// send header | |||||
SendHeader(); | |||||
} | |||||
} | |||||
catch (ArgumentException) | |||||
{ | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
Logging.LogUsefulException(e); | |||||
Close(); | |||||
} | |||||
} | |||||
#endregion | |||||
#region CONNECT | |||||
private void SendConnectResponse() | |||||
{ | |||||
var b = Encoding.UTF8.GetBytes(HTTP_CONNECT_200); | |||||
_localSocket.BeginSend(b, 0, b.Length, SocketFlags.None, Http200SendCallback, null); | |||||
} | |||||
private void Http200SendCallback(IAsyncResult ar) | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
_localSocket.EndSend(ar); | |||||
StartPipe(); | |||||
} | |||||
catch (ArgumentException) | |||||
{ | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
Logging.LogUsefulException(e); | |||||
Close(); | |||||
} | |||||
} | |||||
#endregion | |||||
#region Other http method except CONNECT | |||||
private void SendHeader() | |||||
{ | |||||
var h = _headers.Dequeue() + HTTP_CRLF; | |||||
var len = Encoding.UTF8.GetBytes(h, 0, h.Length, _connetionRecvBuffer, 0); | |||||
_socks5.BeginSend(_connetionRecvBuffer, 0, len, SocketFlags.None, HeaderSendCallback, null); | |||||
} | |||||
private void HeaderSendCallback(IAsyncResult ar) | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
_socks5.EndSend(ar); | |||||
if (_headers.Count > 0) | |||||
{ | |||||
SendHeader(); | |||||
} | |||||
else | |||||
{ | |||||
StartPipe(); | |||||
} | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
Logging.LogUsefulException(e); | |||||
Close(); | |||||
} | |||||
} | |||||
#endregion | |||||
#region Pipe | |||||
private void StartPipe() | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
_socks5.BeginReceive(_remoteRecvBuffer, 0, RecvSize, 0, | |||||
PipeRemoteReceiveCallback, null); | |||||
if (_lastBytesLength > 0) | |||||
{ | |||||
_socks5.BeginSend(_lastBytes, _lastBytesIndex, _lastBytesLength, SocketFlags.None, | |||||
PipeRemoteSendCallback, null); | |||||
} | |||||
else | |||||
{ | |||||
_localSocket.BeginReceive(_connetionRecvBuffer, 0, RecvSize, 0, | |||||
PipeConnectionReceiveCallback, null); | |||||
} | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
Logging.LogUsefulException(e); | |||||
Close(); | |||||
} | |||||
} | |||||
private void PipeRemoteReceiveCallback(IAsyncResult ar) | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
int bytesRead = _socks5.EndReceive(ar); | |||||
Logging.Debug("Read from remote: " + bytesRead); | |||||
if (bytesRead > 0) | |||||
{ | |||||
_localSocket.BeginSend(_remoteRecvBuffer, 0, bytesRead, 0, PipeConnectionSendCallback, null); | |||||
} | |||||
else | |||||
{ | |||||
_localSocket.Shutdown(SocketShutdown.Send); | |||||
_localShutdown = true; | |||||
CheckClose(); | |||||
} | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
Logging.LogUsefulException(e); | |||||
Close(); | |||||
} | |||||
} | |||||
private void PipeConnectionReceiveCallback(IAsyncResult ar) | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
int bytesRead = _localSocket.EndReceive(ar); | |||||
Logging.Debug("Read from local: " + bytesRead); | |||||
if (bytesRead > 0) | |||||
{ | |||||
_socks5.BeginSend(_connetionRecvBuffer, 0, bytesRead, 0, PipeRemoteSendCallback, null); | |||||
} | |||||
else | |||||
{ | |||||
_socks5.Shutdown(SocketShutdown.Send); | |||||
_remoteShutdown = true; | |||||
CheckClose(); | |||||
} | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
Logging.LogUsefulException(e); | |||||
Close(); | |||||
} | |||||
} | |||||
private void PipeRemoteSendCallback(IAsyncResult ar) | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
_socks5.EndSend(ar); | |||||
_localSocket.BeginReceive(_connetionRecvBuffer, 0, RecvSize, 0, | |||||
PipeConnectionReceiveCallback, null); | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
Logging.LogUsefulException(e); | |||||
Close(); | |||||
} | |||||
} | |||||
private void PipeConnectionSendCallback(IAsyncResult ar) | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
_localSocket.EndSend(ar); | |||||
_socks5.BeginReceive(_remoteRecvBuffer, 0, RecvSize, 0, | |||||
PipeRemoteReceiveCallback, null); | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
Logging.LogUsefulException(e); | |||||
Close(); | |||||
} | |||||
} | |||||
#endregion | |||||
#region Header Parse | |||||
private void OnException(Exception ex, object state) | |||||
{ | |||||
throw ex; | |||||
} | |||||
private static readonly Regex HttpRequestHeaderRegex = new Regex(@"^([A-Z]+?) ([^\s]+) HTTP/1\.\d$"); | |||||
private int _requestLineCount = 0; | |||||
private volatile bool _isConnect = false; | |||||
private string _targetHost; | |||||
private int _targetPort; | |||||
private readonly Queue<string> _headers = new Queue<string>(); | |||||
private bool OnLineRead(string line, object state) | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return true; | |||||
} | |||||
Logging.Debug(line); | |||||
if (_requestLineCount == 0) | |||||
{ | |||||
var m = HttpRequestHeaderRegex.Match(line); | |||||
if (m.Success) | |||||
{ | |||||
var method = m.Groups[1].Value; | |||||
if (method == "CONNECT") | |||||
{ | |||||
_isConnect = true; | |||||
var location = m.Groups[2].Value; | |||||
var locs = location.Split(':'); | |||||
_targetHost = locs[0]; | |||||
if (locs.Length > 1) | |||||
{ | |||||
if (!int.TryParse(locs[1], out _targetPort)) | |||||
{ | |||||
throw new Exception("Bad http header: " + line); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
_targetPort = 80; | |||||
} | |||||
} | |||||
_headers.Enqueue(line); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (line.IsNullOrEmpty()) | |||||
{ | |||||
_headers.Enqueue(""); | |||||
return true; | |||||
} | |||||
if (!line.StartsWith("Proxy-")) | |||||
{ | |||||
_headers.Enqueue(line); | |||||
} | |||||
if (!_isConnect) | |||||
{ | |||||
if (line.StartsWith("Host: ")) | |||||
{ | |||||
var location = line.Substring(6).Trim(); | |||||
var locs = location.Split(':'); | |||||
_targetHost = locs[0]; | |||||
if (locs.Length > 1) | |||||
{ | |||||
if (!int.TryParse(locs[1], out _targetPort)) | |||||
{ | |||||
throw new Exception("Bad http header: " + line); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
_targetPort = 80; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
_requestLineCount++; | |||||
return false; | |||||
} | |||||
private void OnFinish(byte[] lastBytes, int index, int length, object state) | |||||
{ | |||||
if (_closed) | |||||
{ | |||||
return; | |||||
} | |||||
if (_targetHost == null) | |||||
{ | |||||
Logging.Error("Unkonwn host"); | |||||
Close(); | |||||
} | |||||
else | |||||
{ | |||||
if (length > 0) | |||||
{ | |||||
_lastBytes = lastBytes; | |||||
_lastBytesIndex = index; | |||||
_lastBytesLength = length; | |||||
} | |||||
// Start socks5 conn | |||||
_socks5 = new Socks5Proxy(); | |||||
_socks5.BeginConnectProxy(SocketUtil.GetEndPoint("127.0.0.1", _socks5Port), ProxyConnectCallback, | |||||
null); | |||||
} | |||||
} | |||||
#endregion | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -7,7 +7,7 @@ using System.Text; | |||||
using System.Threading; | using System.Threading; | ||||
using System.Web; | using System.Web; | ||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using Shadowsocks.Controller.Service; | |||||
using Shadowsocks.Controller.Strategy; | using Shadowsocks.Controller.Strategy; | ||||
using Shadowsocks.Model; | using Shadowsocks.Model; | ||||
using Shadowsocks.Properties; | using Shadowsocks.Properties; | ||||
@@ -462,6 +462,7 @@ namespace Shadowsocks.Controller | |||||
services.Add(tcpRelay); | services.Add(tcpRelay); | ||||
services.Add(udpRelay); | services.Add(udpRelay); | ||||
services.Add(_pacServer); | services.Add(_pacServer); | ||||
services.Add(new Http2Socks5(_config.localPort)); | |||||
_listener = new Listener(services); | _listener = new Listener(services); | ||||
_listener.Start(_config); | _listener.Start(_config); | ||||
} | } | ||||
@@ -0,0 +1,117 @@ | |||||
using System; | |||||
namespace Shadowsocks.Util.Sockets | |||||
{ | |||||
// Boyer-Moore string search | |||||
public static class ByteSearch | |||||
{ | |||||
public class SearchTarget | |||||
{ | |||||
private readonly byte[] _needle; | |||||
private readonly int[] _needleCharTable; | |||||
private readonly int[] _needleOffsetTable; | |||||
public SearchTarget(byte[] needle) | |||||
{ | |||||
_needle = needle; | |||||
_needleCharTable = MakeCharTable(needle); | |||||
_needleOffsetTable = MakeOffsetTable(needle); | |||||
} | |||||
// Thread-safe | |||||
public int SearchIn(byte[] haystack, int index, int length) | |||||
{ | |||||
return IndexOf(haystack, index, length, _needle, _needleOffsetTable, _needleCharTable); | |||||
} | |||||
} | |||||
private static int IndexOf(byte[] haystack, int index, int length, byte[] needle, int[] offsetTable, int[] charTable) | |||||
{ | |||||
var end = index + length; | |||||
for (int i = needle.Length - 1 + index, j; i < end;) | |||||
{ | |||||
for (j = needle.Length - 1; needle[j] == haystack[i]; --i, --j) | |||||
{ | |||||
if (j == 0) | |||||
{ | |||||
return i; | |||||
} | |||||
} | |||||
// i += needle.length - j; // For naive method | |||||
i += Math.Max(offsetTable[needle.Length - 1 - j], charTable[haystack[i]]); | |||||
} | |||||
return -1; | |||||
} | |||||
/** | |||||
* Makes the jump table based on the mismatched character information. | |||||
*/ | |||||
private static int[] MakeCharTable(byte[] needle) | |||||
{ | |||||
const int ALPHABET_SIZE = 256; | |||||
int[] table = new int[ALPHABET_SIZE]; | |||||
for (int i = 0; i < table.Length; ++i) | |||||
{ | |||||
table[i] = needle.Length; | |||||
} | |||||
for (int i = 0; i < needle.Length - 1; ++i) | |||||
{ | |||||
table[needle[i]] = needle.Length - 1 - i; | |||||
} | |||||
return table; | |||||
} | |||||
/** | |||||
* Makes the jump table based on the scan offset which mismatch occurs. | |||||
*/ | |||||
private static int[] MakeOffsetTable(byte[] needle) | |||||
{ | |||||
int[] table = new int[needle.Length]; | |||||
int lastPrefixPosition = needle.Length; | |||||
for (int i = needle.Length - 1; i >= 0; --i) | |||||
{ | |||||
if (IsPrefix(needle, i + 1)) | |||||
{ | |||||
lastPrefixPosition = i + 1; | |||||
} | |||||
table[needle.Length - 1 - i] = lastPrefixPosition - i + needle.Length - 1; | |||||
} | |||||
for (int i = 0; i < needle.Length - 1; ++i) | |||||
{ | |||||
int slen = SuffixLength(needle, i); | |||||
table[slen] = needle.Length - 1 - i + slen; | |||||
} | |||||
return table; | |||||
} | |||||
/** | |||||
* Is needle[p:end] a prefix of needle? | |||||
*/ | |||||
private static bool IsPrefix(byte[] needle, int p) | |||||
{ | |||||
for (int i = p, j = 0; i < needle.Length; ++i, ++j) | |||||
{ | |||||
if (needle[i] != needle[j]) | |||||
{ | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
/** | |||||
* Returns the maximum length of the substring ends at p and is a suffix. | |||||
*/ | |||||
private static int SuffixLength(byte[] needle, int p) | |||||
{ | |||||
int len = 0; | |||||
for (int i = p, j = needle.Length - 1; | |||||
i >= 0 && needle[i] == needle[j]; --i, --j) | |||||
{ | |||||
len += 1; | |||||
} | |||||
return len; | |||||
} | |||||
} | |||||
} |
@@ -12,8 +12,7 @@ namespace Shadowsocks.Util.Sockets | |||||
private readonly Encoding _encoding; | private readonly Encoding _encoding; | ||||
// private readonly string _delimiter; | // private readonly string _delimiter; | ||||
private readonly byte[] _delimiterBytes; | private readonly byte[] _delimiterBytes; | ||||
private readonly int[] _delimiterSearchCharTable; | |||||
private readonly int[] _delimiterSearchOffsetTable; | |||||
private readonly ByteSearch.SearchTarget _delimiterSearch; | |||||
private readonly object _state; | private readonly object _state; | ||||
private readonly byte[] _lineBuffer; | private readonly byte[] _lineBuffer; | ||||
@@ -78,8 +77,7 @@ namespace Shadowsocks.Util.Sockets | |||||
throw new ArgumentException("Too small!", nameof(maxLineBytes)); | throw new ArgumentException("Too small!", nameof(maxLineBytes)); | ||||
} | } | ||||
_delimiterSearchCharTable = MakeCharTable(_delimiterBytes); | |||||
_delimiterSearchOffsetTable = MakeOffsetTable(_delimiterBytes); | |||||
_delimiterSearch = new ByteSearch.SearchTarget(_delimiterBytes); | |||||
_lineBuffer = new byte[maxLineBytes]; | _lineBuffer = new byte[maxLineBytes]; | ||||
@@ -140,8 +138,7 @@ namespace Shadowsocks.Util.Sockets | |||||
private void NewPackageRecv() | private void NewPackageRecv() | ||||
{ | { | ||||
int i; | int i; | ||||
while ((i = IndexOf(_lineBuffer, _bufferDataIndex, _bufferDataLength, _delimiterBytes, _delimiterSearchOffsetTable, | |||||
_delimiterSearchCharTable)) != -1) | |||||
while ((i = _delimiterSearch.SearchIn(_lineBuffer, _bufferDataIndex, _bufferDataLength)) != -1) | |||||
{ | { | ||||
var decodeLen = i - _bufferDataIndex; | var decodeLen = i - _bufferDataIndex; | ||||
string line = _encoding.GetString(_lineBuffer, _bufferDataIndex, decodeLen); | string line = _encoding.GetString(_lineBuffer, _bufferDataIndex, decodeLen); | ||||
@@ -183,97 +180,5 @@ namespace Shadowsocks.Util.Sockets | |||||
{ | { | ||||
_onFinish?.Invoke(_lineBuffer, _bufferDataIndex, _bufferDataLength, _state); | _onFinish?.Invoke(_lineBuffer, _bufferDataIndex, _bufferDataLength, _state); | ||||
} | } | ||||
#region Boyer-Moore string search | |||||
private static int IndexOf(byte[] haystack, int index, int length, byte[] needle, int[] offsetTable, int[] charTable) | |||||
{ | |||||
var end = index + length; | |||||
for (int i = needle.Length - 1 + index, j; i < end;) | |||||
{ | |||||
for (j = needle.Length - 1; needle[j] == haystack[i]; --i, --j) | |||||
{ | |||||
if (j == 0) | |||||
{ | |||||
return i; | |||||
} | |||||
} | |||||
// i += needle.length - j; // For naive method | |||||
i += Math.Max(offsetTable[needle.Length - 1 - j], charTable[haystack[i]]); | |||||
} | |||||
return -1; | |||||
} | |||||
/** | |||||
* Makes the jump table based on the mismatched character information. | |||||
*/ | |||||
private static int[] MakeCharTable(byte[] needle) | |||||
{ | |||||
const int ALPHABET_SIZE = 256; | |||||
int[] table = new int[ALPHABET_SIZE]; | |||||
for (int i = 0; i < table.Length; ++i) | |||||
{ | |||||
table[i] = needle.Length; | |||||
} | |||||
for (int i = 0; i < needle.Length - 1; ++i) | |||||
{ | |||||
table[needle[i]] = needle.Length - 1 - i; | |||||
} | |||||
return table; | |||||
} | |||||
/** | |||||
* Makes the jump table based on the scan offset which mismatch occurs. | |||||
*/ | |||||
private static int[] MakeOffsetTable(byte[] needle) | |||||
{ | |||||
int[] table = new int[needle.Length]; | |||||
int lastPrefixPosition = needle.Length; | |||||
for (int i = needle.Length - 1; i >= 0; --i) | |||||
{ | |||||
if (IsPrefix(needle, i + 1)) | |||||
{ | |||||
lastPrefixPosition = i + 1; | |||||
} | |||||
table[needle.Length - 1 - i] = lastPrefixPosition - i + needle.Length - 1; | |||||
} | |||||
for (int i = 0; i < needle.Length - 1; ++i) | |||||
{ | |||||
int slen = SuffixLength(needle, i); | |||||
table[slen] = needle.Length - 1 - i + slen; | |||||
} | |||||
return table; | |||||
} | |||||
/** | |||||
* Is needle[p:end] a prefix of needle? | |||||
*/ | |||||
private static bool IsPrefix(byte[] needle, int p) | |||||
{ | |||||
for (int i = p, j = 0; i < needle.Length; ++i, ++j) | |||||
{ | |||||
if (needle[i] != needle[j]) | |||||
{ | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
/** | |||||
* Returns the maximum length of the substring ends at p and is a suffix. | |||||
*/ | |||||
private static int SuffixLength(byte[] needle, int p) | |||||
{ | |||||
int len = 0; | |||||
for (int i = p, j = needle.Length - 1; | |||||
i >= 0 && needle[i] == needle[j]; --i, --j) | |||||
{ | |||||
len += 1; | |||||
} | |||||
return len; | |||||
} | |||||
#endregion | |||||
} | } | ||||
} | } |
@@ -26,6 +26,14 @@ namespace Shadowsocks.Util.Sockets | |||||
private Socket _activeSocket; | private Socket _activeSocket; | ||||
public WrappedSocket() { } | |||||
public WrappedSocket(Socket socket) | |||||
{ | |||||
_activeSocket = socket; | |||||
} | |||||
public void BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state) | public void BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state) | ||||
{ | { | ||||
if (_disposed) | if (_disposed) | ||||
@@ -190,6 +190,7 @@ | |||||
<Compile Include="StringEx.cs" /> | <Compile Include="StringEx.cs" /> | ||||
<Compile Include="Controller\System\Hotkeys\Hotkeys.cs" /> | <Compile Include="Controller\System\Hotkeys\Hotkeys.cs" /> | ||||
<Compile Include="Encryption\RNG.cs" /> | <Compile Include="Encryption\RNG.cs" /> | ||||
<Compile Include="Util\Sockets\ByteSearch.cs" /> | |||||
<Compile Include="Util\Sockets\LineReader.cs" /> | <Compile Include="Util\Sockets\LineReader.cs" /> | ||||
<Compile Include="Util\Sockets\SocketUtil.cs" /> | <Compile Include="Util\Sockets\SocketUtil.cs" /> | ||||
<Compile Include="Util\Sockets\WrappedSocket.cs" /> | <Compile Include="Util\Sockets\WrappedSocket.cs" /> | ||||