|
|
@@ -1,26 +1,15 @@ |
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Linq; |
|
|
|
using System.Net.Sockets; |
|
|
|
using System.Text; |
|
|
|
using System.Text.RegularExpressions; |
|
|
|
using Shadowsocks.ForwardProxy; |
|
|
|
using System.Threading.Tasks; |
|
|
|
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) |
|
|
@@ -28,465 +17,7 @@ namespace Shadowsocks.Controller.Service |
|
|
|
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<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 |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |