我写了一个简单的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, 0, bytes.Length);
var data = Encoding.ASCII.GetString(bytes, 0, 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 string host, out int port)
{
host = null;
port = 0;
var lines = data.Split("rn", 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, 0, 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 OKrnContent-Type: application/octet-streamrnrn").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, 0, sendBuffer.Length)) != 0)
{
var data = Encoding.ASCII.GetString(sendBuffer, 0, r);
//Console.WriteLine("Received: {0}", data);
await targetStream.WriteAsync(sendBuffer, 0, 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, 0, receiveBuffer.Length)) != 0)
{
//var data2 = Encoding.ASCII.GetString(receiveBuffer, 0, i);
//Console.WriteLine("Remote: {0}", data2);
await stream.WriteAsync(receiveBuffer, 0, i);
}
}
catch (Exception ex)
{
Console.WriteLine("Receive Connection Failed: " + ex.Message);
}
}
}
}
和简单地使用in 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则没有。
在这里你可以检查你的http隧道服务器不能很好地工作的网站的TLS版本,并将它们与工作的网站进行比较。
经过几个小时的尝试和错误,我发现我应该使用更大的缓冲区大小!
改变这一行解决了这个问题:
const int BufferSize = 8192;
我不知道为什么,但显然SSL握手不工作时,发送多个块的数据在较小的缓冲区大小