问题描述
我写了一个简单的http隧道代理服务器:
using System;
using System.Net;
using System.Net.sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace TrafficMonitor
{
public class ProxyServer
{
const int BufferSize = 255;
public int Port { get; set; } = 9999;
private static Regex httpRegex = new Regex(@"^(?<method>[a-zA-Z]+)\s(?<url>.+)\sHTTP/(?<major>\d)\.(?<minor>\d+)$");
public async Task RunServerForEver()
{
var listner = new TcpListener(IPAddress.Any,Port);
listner.Start();
Console.WriteLine($"Listening on port {Port}");
try
{
while (true)
{
var client = await listner.AcceptTcpClientAsync();
ThreadPool.QueueUserWorkItem(HandleClient,client);
}
}
finally
{
listner.Stop();
Console.WriteLine("Server stopped");
}
}
void HandleClient(object state)
{
TcpClient client = (TcpClient)state;
Console.WriteLine($"New Connection Received: #{client.Client.Handle}");
try
{
byte[] bytes = new byte[BufferSize];
using (client)
using (var stream = client.GetStream())
{
int i = stream.Read(bytes,bytes.Length);
var data = Encoding.ASCII.GetString(bytes,i);
Console.WriteLine("Received: {0}",data);
if (!ParseHostPort(data,out string host,out int port))
{
client.Close();
return;
};
HandleClientIO(stream,host,port);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
bool ParseHostPort(string data,out int port)
{
host = null;
port = 0;
var lines = data.Split("\r\n",StringSplitOptions.RemoveEmptyEntries);
if (lines.Length == 0)
{
return false;
}
var connectMatch = httpRegex.Match(lines[0]);
if (!connectMatch.Success)
{
return false;
}
if (connectMatch.Groups["method"].Value != "CONNECT")
{
return false;
}
var url = connectMatch.Groups["url"].Value;
var uri = new Uri("http://" + url);
host = uri.Host;
port = uri.Port;
return true;
}
async Task SendMessage(NetworkStream stream,string data)
{
byte[] msg = Encoding.ASCII.GetBytes(data);
await stream.WriteAsync(msg,msg.Length);
}
void HandleClientIO(NetworkStream stream,string host,int port)
{
TcpClient client = new TcpClient();
if (!client.ConnectAsync(host,port).Wait(10000))
{
throw new Exception("Could not connect to host: " + host + ":" + port);
}
SendMessage(stream,"HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\n\r\n").Wait();
using (var targetStream = client.GetStream())
{
Task send = SendClientData(stream,targetStream);
Task receive = ReceiveClientData(stream,targetStream);
Parallel.Invoke(
async () => await send,async () => await receive
);
Task.WaitAll(send,receive);
}
}
async Task SendClientData(NetworkStream stream,NetworkStream targetStream)
{
await Task.Yield();
try
{
int r;
byte[] sendBuffer = new byte[BufferSize];
while ((r = await stream.ReadAsync(sendBuffer,sendBuffer.Length)) != 0)
{
var data = Encoding.ASCII.GetString(sendBuffer,r);
//Console.WriteLine("Received: {0}",data);
await targetStream.WriteAsync(sendBuffer,r);
}
}
catch (Exception ex)
{
Console.WriteLine("Send Connection Failed: " + ex.Message);
}
}
async Task ReceiveClientData(NetworkStream stream,NetworkStream targetStream)
{
await Task.Yield();
try
{
byte[] receiveBuffer = new byte[BufferSize];
int i;
while ((i = await targetStream.ReadAsync(receiveBuffer,receiveBuffer.Length)) != 0)
{
//var data2 = Encoding.ASCII.GetString(receiveBuffer,i);
//Console.WriteLine("Remote: {0}",data2);
await stream.WriteAsync(receiveBuffer,i);
}
}
catch (Exception ex)
{
Console.WriteLine("Receive Connection Failed: " + ex.Message);
}
}
}
}
并简单地在 main 中使用:
class Program
{
static void Main(string[] args)
{
var server = new ProxyServer();
server.RunServerForEver().Wait();
}
}
我执行代理并在 Chrome 和 Firefox 中对其进行测试,它适用于许多 https 和 http 网站。但不幸的是,有些网站不响应初始 SSL 握手,代理等待响应直到超时。
例如“https://google.com”有效,但 https://github.com/
无效。
解决方法
可能与网站支持的TLS版本有关。
尽管 TLS 1.0 和 TLS 1.1 已被弃用,但 google 仍然保持启用它们而 github 没有。
Here 您可以检查您的 http 隧道服务器无法正常工作的网站的 TLS 版本,并将其与正常工作的网站进行比较。
,经过几个小时的尝试和错误,我发现我应该使用更大的缓冲区大小!
改变这一行解决了问题:
const int BufferSize = 8192;
我不知道为什么,但在以较小的缓冲区大小发送多个数据块时,显然 SSL 握手不起作用。