using System; using System.Collections.Generic; using System.Net.Sockets; using System.Text; 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) { return false; } 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 (!line.StartsWith("Proxy-")) { _headers.Enqueue(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; } } } } else { if (line.IsNullOrEmpty()) { return true; } 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 } } }