diff --git a/shadowsocks-csharp/Controller/Service/Http2Socks5.cs b/shadowsocks-csharp/Controller/Service/Http2Socks5.cs index 9c557246..46d68b73 100644 --- a/shadowsocks-csharp/Controller/Service/Http2Socks5.cs +++ b/shadowsocks-csharp/Controller/Service/Http2Socks5.cs @@ -1,15 +1,26 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net.Sockets; using System.Text; -using System.Threading.Tasks; +using System.Text.RegularExpressions; +using Shadowsocks.ForwardProxy; using Shadowsocks.Util.Sockets; namespace Shadowsocks.Controller.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) { if (socket.ProtocolType != ProtocolType.Tcp) @@ -17,7 +28,465 @@ namespace Shadowsocks.Controller.Service 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 _headers = new Queue(); + + 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 + } } } diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index 1ae747ae..0b35cfe2 100644 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -7,7 +7,7 @@ using System.Text; using System.Threading; using System.Web; using Newtonsoft.Json; - +using Shadowsocks.Controller.Service; using Shadowsocks.Controller.Strategy; using Shadowsocks.Model; using Shadowsocks.Properties; @@ -462,6 +462,7 @@ namespace Shadowsocks.Controller services.Add(tcpRelay); services.Add(udpRelay); services.Add(_pacServer); + services.Add(new Http2Socks5(_config.localPort)); _listener = new Listener(services); _listener.Start(_config); } diff --git a/shadowsocks-csharp/Util/Sockets/ByteSearch.cs b/shadowsocks-csharp/Util/Sockets/ByteSearch.cs new file mode 100644 index 00000000..21bca3f7 --- /dev/null +++ b/shadowsocks-csharp/Util/Sockets/ByteSearch.cs @@ -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; + } + } +} diff --git a/shadowsocks-csharp/Util/Sockets/LineReader.cs b/shadowsocks-csharp/Util/Sockets/LineReader.cs index f51a9b71..9bd40321 100644 --- a/shadowsocks-csharp/Util/Sockets/LineReader.cs +++ b/shadowsocks-csharp/Util/Sockets/LineReader.cs @@ -12,8 +12,7 @@ namespace Shadowsocks.Util.Sockets private readonly Encoding _encoding; // private readonly string _delimiter; private readonly byte[] _delimiterBytes; - private readonly int[] _delimiterSearchCharTable; - private readonly int[] _delimiterSearchOffsetTable; + private readonly ByteSearch.SearchTarget _delimiterSearch; private readonly object _state; private readonly byte[] _lineBuffer; @@ -78,8 +77,7 @@ namespace Shadowsocks.Util.Sockets throw new ArgumentException("Too small!", nameof(maxLineBytes)); } - _delimiterSearchCharTable = MakeCharTable(_delimiterBytes); - _delimiterSearchOffsetTable = MakeOffsetTable(_delimiterBytes); + _delimiterSearch = new ByteSearch.SearchTarget(_delimiterBytes); _lineBuffer = new byte[maxLineBytes]; @@ -140,8 +138,7 @@ namespace Shadowsocks.Util.Sockets private void NewPackageRecv() { int i; - while ((i = IndexOf(_lineBuffer, _bufferDataIndex, _bufferDataLength, _delimiterBytes, _delimiterSearchOffsetTable, - _delimiterSearchCharTable)) != -1) + while ((i = _delimiterSearch.SearchIn(_lineBuffer, _bufferDataIndex, _bufferDataLength)) != -1) { var decodeLen = i - _bufferDataIndex; string line = _encoding.GetString(_lineBuffer, _bufferDataIndex, decodeLen); @@ -183,97 +180,5 @@ namespace Shadowsocks.Util.Sockets { _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 } } diff --git a/shadowsocks-csharp/Util/Sockets/WrappedSocket.cs b/shadowsocks-csharp/Util/Sockets/WrappedSocket.cs index 85971ced..db4332af 100644 --- a/shadowsocks-csharp/Util/Sockets/WrappedSocket.cs +++ b/shadowsocks-csharp/Util/Sockets/WrappedSocket.cs @@ -26,6 +26,14 @@ namespace Shadowsocks.Util.Sockets private Socket _activeSocket; + public WrappedSocket() { } + + public WrappedSocket(Socket socket) + { + _activeSocket = socket; + } + + public void BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state) { if (_disposed) diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 1a4558a5..f663e8cd 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -190,6 +190,7 @@ +