I want to write a simple http proxy server in C#. My proxy server is already able to get requests from the client and forward them to the host. It should also be already able to forward Responses from the host to the client. However, not all requests and responses are sent correctly. I want to open simple http websites with it, but it doesn't work right now. I think the problem is that I can't just forward the requests and responses directly. Do I need to adjust them before forwarding them? Or is there a different error? Thanks in advance!
public class ProxyServer
{
private TcpListener listener;
private bool serverIsRunning;
public void Start()
{
this.listener = new TcpListener(IPAddress.Any, 5000);
this.listener.Start();
this.serverIsRunning = true;
while (this.serverIsRunning)
{
if (!listener.Pending())
{
Thread.Sleep(100);
continue;
}
TcpClient client = listener.AcceptTcpClient();
Thread session = new Thread(new ParameterizedThreadStart(StartNewClientSession));
session.Start(client);
}
}
public void Stop()
{
throw new System.NotImplementedException();
}
public void StartNewClientSession(object data)
{
TcpClient client = (TcpClient)data;
NetworkStream clientStream = client.GetStream();
while (true)
{
// Get HTTP request from client
byte[] receiveBuffer = null;
if (clientStream.CanRead && clientStream != null)
{
receiveBuffer = NetworkManager.ReadMessage(clientStream);
}
else
{
continue;
}
string request = Encoding.ASCII.GetString(receiveBuffer);
Console.WriteLine(request);
string[] splittedRequest = request.Split(new char[0]);
string host = GetHostFromRequest(splittedRequest);
if (host == string.Empty)
{
continue;
}
TcpClient server = new TcpClient(host, 80);
NetworkStream serverStream = server.GetStream();
// Forward HTTP request to server
if (serverStream.CanWrite && serverStream != null)
{
NetworkManager.SendMessage(serverStream, receiveBuffer);
}
else
{
continue;
}
// Get HTTP response from server
if (serverStream.CanRead && serverStream != null)
{
receiveBuffer = NetworkManager.ReadMessage(serverStream);
}
else
{
continue;
}
string response = Encoding.ASCII.GetString(receiveBuffer);
Console.WriteLine(response);
// Forward HTTP response to client
if (clientStream.CanWrite && clientStream != null)
{
NetworkManager.SendMessage(clientStream, receiveBuffer);
}
else
{
continue;
}
}
}
public string GetHostFromRequest(string[] request)
{
for (int i = 0; i < request.Length; i++)
{
if (request[i] == "Host:")
{
string[] checkHost = request[i + 1].Split(':');
if (checkHost.Length != 1)
{
return checkHost[0];
}
else
{
return request[i + 1];
}
}
}
return string.Empty;
}
}
public class NetworkManager
{
public static byte[] ReadMessage(NetworkStream stream)
{
byte[] receiveBuffer = new byte[8192];
if (stream.CanRead && stream != null)
{
int receivedBytes = stream.Read(receiveBuffer, 0, receiveBuffer.Length);
}
return receiveBuffer;
}
public static void SendMessage(NetworkStream stream, byte[] sendBuffer)
{
if (stream.CanWrite && stream != null)
{
stream.Write(sendBuffer, 0, sendBuffer.Length);
}
}
}
Related
I'm trying to make client get some data from server using TCP.
But it works only once. Then stream.DataAvailable is always false.
Client code:
while (!StopEvent.WaitOne(WaitTime, true))
{
try
{
if (TcpClient == null || !TcpClient.Connected)
{
if (TcpClient != null)
{
TcpClient.Close();
TcpClient = null;
}
TcpClient = new TcpClient(MasterHost, MasterMonitoringPort) {NoDelay = true};
}
var stream = TcpClient.GetStream();
stream.WriteTimeout = TimeoutMs;
stream.ReadTimeout = TimeoutMs;
stream.Write(GetMasterStateRequestBytes, 0, GetMasterStateRequestBytes.Length);
var serialisedDataBuilder = new StringBuilder();
if (stream.DataAvailable)
{
while (stream.DataAvailable)
{
var bytesRead = stream.Read(BytesBuffer, 0, BytesBuffer.Length);
serialisedDataBuilder.Append(Encoding.UTF8.GetString(BytesBuffer, 0, bytesRead));
}
var responses = MonitoringResponse.StringToResponses(serialisedDataBuilder.ToString());
foreach (var response in responses)
{
if (response.MonitoringResponseType == MonitoringResponseType.ProvideMasterStateInfo && response.Parameters is MasterStateInfo masterStateInfo)
MasterStateInfo = masterStateInfo;
}
}
}
catch (Exception exception)
{
LastException = exception;
TcpClient?.Close();
TcpClient = null;
}
Thread.Sleep(10*1000);
}
Server code :
while (!StopEvent.WaitOne(WaitTime, true))
{
try
{
if (TcpListener == null)
{
Application.Tracer.Trace(this, TracerEventKind.Info, "Starting TcpListener");
TcpListener = new TcpListener(IPAddress.Any, MasterNetworkServer.MonitoringPort);
TcpListener.Start();
}
if (TcpListener.Pending())
{
Application.Tracer.Trace(this, TracerEventKind.Info, "TcpListener is pending, start processing");
var client = TcpListener.AcceptTcpClient();
var stream = client.GetStream();
stream.WriteTimeout = TimeoutMs;
stream.ReadTimeout = TimeoutMs;
var serialisedDataBuilder = new StringBuilder();
if (stream.DataAvailable)
{
do
{
var bytesRead = stream.Read(BytesBuffer, 0, BytesBuffer.Length);
serialisedDataBuilder.Append(Encoding.UTF8.GetString(BytesBuffer, 0, bytesRead));
} while (stream.DataAvailable);
Application.Tracer.Trace(this, TracerEventKind.Info, "Bytes received");
var requests =
MonitoringRequest.StringToRequests(serialisedDataBuilder.ToString(), distinct: true);
var responses = new List<MonitoringResponse>();
if (requests.Any())
{
Application.Tracer.Trace(this, TracerEventKind.Info,
$"Start to processing {requests.Count} requests");
foreach (var request in requests)
{
responses.Add(HandleMonitoringRequest(request));
Application.Tracer.Trace(this, TracerEventKind.Info, "Response made");
}
Application.Tracer.Trace(this, TracerEventKind.Info, "All responses made");
}
var responsesBytes = MonitoringResponse.ResponsesToBytes(responses);
stream.Write(responsesBytes, 0, responsesBytes.Length);
}
}
}
catch(Exception exception)
{
Application.Tracer.Trace(this, TracerEventKind.Info, $"Monitoring network service exception: {exception.Message}");
}
Thread.Sleep(0);
Avoiding stupid restrictions text.
Avoiding stupid restrictions text.
Avoiding stupid restrictions text.
Avoiding stupid restrictions text.
Avoiding stupid restrictions text.
Avoiding stupid restrictions text.
The way to fix it was to close stream and client (with method .Close()) after each session and also add some sleep before checking isDataAvailable
I have created a simple C# client application. Once it connects to the server it should read the messages sent from the server. It also has the ability to send messages to server too. However I am unable to figure out to correct way to read the data.
I am spawning a thread once it connects to the server. The thread runs in infinite loop and have two interfaces each for reading and writing. Connect() method is called from a ButtonClick event.
My code snippet is as below:
namespace WpfApp1
{
public class TCPClientClass
{
private StreamWriter SwSender;
NetworkStream Sender;
NetworkStream Receiver;
//private StreamReader SrReciever;
private Thread thrMessaging;
TcpClient tcp;
bool connected = false;
public bool Connected { get { return connected; } set { connected = value; } }
//public bool Connect(IPAddress IP, int nPortNo)
public async Task Connect(IPAddress IP, int nPortNo)
{
tcp = new TcpClient();
try
{
//tcp.Connect(strIPAddress.Parse("192.168.137.1"), 2000);
// tcp.Connect(IP , nPortNo);
await tcp.ConnectAsync(IP, nPortNo);
thrMessaging = new Thread(new ThreadStart(ThreadFunction));
thrMessaging.Start();
Connected = true;
}
catch
{
MessageBox.Show("Unable to connect to server");
//return false;
}
//return true;
}
public void Disconnect()
{
Sender?.Close();
Receiver?.Close();
tcp?.Close();
//tcp?.Client.Disconnect(false);
thrMessaging.Abort();
Connected = false;
}
private void ThreadFunction()
{
while (thrMessaging.IsAlive)
DoTasks();
}
private void DoTasks()
{
if (Connected)
{
var a = ReadMessages();
SendMessages();
}
}
private /*void*/async Task ReadMessages()
{
byte[] data = new byte[4096];
//Int32 bytesRead = 0;
//Task<int> bytesReadTask;
String responseData = String.Empty;
Receiver = tcp.GetStream();
try
{
//bytesReadTask = Receiver.ReadAsync(data, 0, data.Length);
//responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytesReadTask.Result);
var response = await Receiver.ReadAsync(data, 0, data.Length);
MessageBox.Show("Server response was " + response);
Thread.Sleep(1000);
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
private void SendMessages()
{
try
{
string strSendData = "Hello from GUI";
Byte[] data = System.Text.Encoding.ASCII.GetBytes(strSendData);
Sender = tcp.GetStream();
Sender.Write(data, 0, data.Length);
Sender.Flush();
Thread.Sleep(1000);
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
}
}
you should change
var response = await Receiver.ReadAsync(data, 0, data.Length);
MessageBox.Show("Server response was " + response);
to
var response = await Receiver.ReadAsync(data, 0, data.Length);
string result = System.Text.Encoding.Default.GetString(data);
MessageBox.Show("Server response was " + result);
if you´re still having problems..my server Code:
public class tcpServer
{
public void method()
{
TcpListener server = new TcpListener(IPAddress.Any, 9999);
server.Start();
TcpClient client = server.AcceptTcpClient();
NetworkStream ns = client.GetStream();
byte[] hello = new byte[100];
hello = Encoding.Default.GetBytes("hello world");
while (client.Connected)
{
ns.Write(hello, 0, hello.Length);
}
}
}
I'm trying to build a simple http proxy, which does four really basic things:
Accepts connection from web-browser (using TcpClient/TcpListener).
Reads request from its stream.
Reads hostname and initiates connection with host.
Loads content from webpage and forwards it back to the client.
The troubles i met with:
Sometimes page wouldn't load at all.
Sometimes browser gives me an error 'The content has wrong encryption'(in firefox).
Seldom i can see content corruption(plain text instead of HTML).
What i've done:
HttpListener class that contains methods for listening for incoming requests and invoking event OnNewRequestReceived:
public void Listen()
{
Listener.Start();
while (true)
{
var client = Listener.AcceptTcpClient();
Task.Run(() => StartReceivingData(client));
}
}
public void StartReceivingData(TcpClient client)
{
NetworkStream clientStream = client.GetStream();
var buffer = new byte[16000];
while (true)
{
try
{
if (!clientStream.CanRead)
return;
//connection is closed
if (clientStream.Read(buffer).Equals(0))
return;
OnNewRequestReceived?.Invoke(this, new RequestReceivedEventArgs() { User = client, Request = buffer });
} // when clientStream is disposed, exception is thrown.
catch { return; }
}
}
HttpClient class which basically contains a method that subscribes to event described above:
private void Listener_OnNewConnectionReceived(object sender, RequestReceivedEventArgs e)
{
string hostname = HttpQueryParser.GetHostName(e.Request);
NetworkStream proxyClientStream = e.User.GetStream();
try
{
if (firewall.CheckIfBlocked(hostname))
{
//send error page
e.User.GetStream().Write(Encoding.ASCII.GetBytes("<html><body style=\"padding:0; margin:0;\"><img style=\"padding:0; margin:0; width:100%; height:100%;\" src=\"https://www.hostinger.co.id/tutorial/wp-content/uploads/sites/11/2017/08/what-is-403-forbidden-error-and-how-to-fix-it.jpg\"</body></html>"));
return;
}
var targetServer = new TcpClient(hostname, 80);
NetworkStream targetServerStream = targetServer.GetStream();
targetServerStream.Write(e.Request);
var responseBuffer = new byte[32];
for (int offsetCounter = 0; true; ++offsetCounter)
{
var bytesRead = targetServerStream.Read(responseBuffer, 0, responseBuffer.Length);
// Console.WriteLine($"Read {bytesRead} from {hostname}.");
if (bytesRead.Equals(0))
return;
proxyClientStream.Write(responseBuffer, 0, responseBuffer.Length);
if (offsetCounter.Equals(0))
{
var headers = Encoding.UTF8.GetString(responseBuffer).Split("\r\n");
logger.Log(new HttpRequestEntry()
{
ResponseCode = headers[0].Substring(headers[0].IndexOf(" ") + 1),
Hostname = hostname
});
}
}
}
catch { return; }
finally { proxyClientStream.Dispose(); }
}
So, i'm guessing there's a problem with my buffer size, but changing it to higher values actually doesn't change anything .
Ok so i don't know what's the problem with my byte arrays was, but i made it work, using Stream.CopyTo , which i was quite surprized about - it works on two NetworkStreams.
Here's working method if you are curious:
private void Listener_OnNewConnectionReceived(object sender, RequestReceivedEventArgs e)
{
string hostname = HttpQueryParser.GetHostName(e.Request);
NetworkStream proxyClientStream = e.User.GetStream();
try
{
if (firewall.CheckIfBlocked(hostname))
{
//send error page
e.User.GetStream().Write(Encoding.ASCII.GetBytes("<html><body style=\"padding:0; margin:0;\"><img style=\"padding:0; margin:0; width:100%; height:100%;\" src=\"https://www.hostinger.co.id/tutorial/wp-content/uploads/sites/11/2017/08/what-is-403-forbidden-error-and-how-to-fix-it.jpg\"</body></html>"));
return;
}
var targetServer = new TcpClient(hostname, 80);
NetworkStream targetServerStream = targetServer.GetStream();
targetServerStream.Write(e.Request);
var responseBuffer = new byte[32];
//this is to capture status of http request and log it.
targetServerStream.Read(responseBuffer, 0, responseBuffer.Length);
proxyClientStream.Write(responseBuffer, 0, responseBuffer.Length);
var headers = Encoding.UTF8.GetString(responseBuffer).Split("\r\n");
logger.Log(new HttpRequestEntry()
{
ResponseCode = headers[0].Substring(headers[0].IndexOf(" ") + 1),
Hostname = hostname
});
targetServerStream.CopyTo(proxyClientStream);
}
catch { return; }
finally { proxyClientStream.Dispose(); }
}
I want to create a windows service from this code. Anyone can help for creating a windows service. I tried many time myself but i get success 50%. In my code i had 3 function that i want to perform after connecting with client application.
public static class Program
{
public static TcpClient client;
private static TcpListener listener;
private static string ipString;
static void Main(string[] args)
{
IPAddress[] localIp = Dns.GetHostAddresses(Dns.GetHostName());
foreach (IPAddress address in localIp)
{
if (address.AddressFamily == AddressFamily.InterNetwork)
{
ipString = address.ToString();
}
}
IPEndPoint ep = new IPEndPoint(IPAddress.Parse(ipString), 1234);
listener = new TcpListener(ep);
listener.Start();
client = listener.AcceptTcpClient();
while (client.Connected)
{
try
{
const int bytesize = 1024 * 1024;
byte[] buffer = new byte[bytesize];
string x = client.GetStream().Read(buffer, 0, bytesize).ToString();
var data = ASCIIEncoding.ASCII.GetString(buffer);
if (data.ToUpper().Contains("SLP2"))
{
Sleep();
}
else if (data.ToUpper().Contains("SHTD3"))
{
Shutdown();
}
else if (data.ToUpper().Contains("TSC1"))
{
var bitmap = SaveScreenshot();
var stream = new MemoryStream();
bitmap.Save(stream, ImageFormat.Bmp);
sendData(stream.ToArray(), client.GetStream());
}
}
catch (Exception exc)
{
client.Dispose();
client.Close();
}
}
}
}
How can I test SMTP is up and running via C# without sending a message.
I could of course try:
try{
// send email to "nonsense#example.com"
}
catch
{
// log "smtp is down"
}
There must be a more tidy way to do this.
You can try saying EHLO to your server and see if it responds with 250 OK. Of course this test doesn't guarantee you that you will succeed sending the mail later, but it is a good indication.
And here's a sample:
class Program
{
static void Main(string[] args)
{
using (var client = new TcpClient())
{
var server = "smtp.gmail.com";
var port = 465;
client.Connect(server, port);
// As GMail requires SSL we should use SslStream
// If your SMTP server doesn't support SSL you can
// work directly with the underlying stream
using (var stream = client.GetStream())
using (var sslStream = new SslStream(stream))
{
sslStream.AuthenticateAsClient(server);
using (var writer = new StreamWriter(sslStream))
using (var reader = new StreamReader(sslStream))
{
writer.WriteLine("EHLO " + server);
writer.Flush();
Console.WriteLine(reader.ReadLine());
// GMail responds with: 220 mx.google.com ESMTP
}
}
}
}
}
And here's the list of codes to expect.
I use this method and classes to validate the credentials (link to github):
public static bool ValidateCredentials(string login, string password, string server, int port, bool enableSsl) {
SmtpConnectorBase connector;
if (enableSsl) {
connector = new SmtpConnectorWithSsl(server, port);
} else {
connector = new SmtpConnectorWithoutSsl(server, port);
}
if (!connector.CheckResponse(220)) {
return false;
}
connector.SendData($"HELO {Dns.GetHostName()}{SmtpConnectorBase.EOF}");
if (!connector.CheckResponse(250)) {
return false;
}
connector.SendData($"AUTH LOGIN{SmtpConnectorBase.EOF}");
if (!connector.CheckResponse(334)) {
return false;
}
connector.SendData(Convert.ToBase64String(Encoding.UTF8.GetBytes($"{login}")) + SmtpConnectorBase.EOF);
if (!connector.CheckResponse(334)) {
return false;
}
connector.SendData(Convert.ToBase64String(Encoding.UTF8.GetBytes($"{password}")) + SmtpConnectorBase.EOF);
if (!connector.CheckResponse(235)) {
return false;
}
return true;
}
SmtpConnectorBase:
internal abstract class SmtpConnectorBase {
protected string SmtpServerAddress { get; set; }
protected int Port { get; set; }
public const string EOF = "\r\n";
protected SmtpConnectorBase(string smtpServerAddress, int port) {
SmtpServerAddress = smtpServerAddress;
Port = port;
}
public abstract bool CheckResponse(int expectedCode);
public abstract void SendData(string data);
}
SmtpConnectorWithoutSsl:
internal class SmtpConnectorWithoutSsl : SmtpConnectorBase {
private Socket _socket = null;
public SmtpConnectorWithoutSsl(string smtpServerAddress, int port) : base(smtpServerAddress, port) {
IPHostEntry hostEntry = Dns.GetHostEntry(smtpServerAddress);
IPEndPoint endPoint = new IPEndPoint(hostEntry.AddressList[0], port);
_socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
//try to connect and test the rsponse for code 220 = success
_socket.Connect(endPoint);
}
~SmtpConnectorWithoutSsl() {
try {
if (_socket != null) {
_socket.Close();
_socket.Dispose();
_socket = null;
}
} catch (Exception) {
;
}
}
public override bool CheckResponse(int expectedCode) {
while (_socket.Available == 0) {
System.Threading.Thread.Sleep(100);
}
byte[] responseArray = new byte[1024];
_socket.Receive(responseArray, 0, _socket.Available, SocketFlags.None);
string responseData = Encoding.UTF8.GetString(responseArray);
int responseCode = Convert.ToInt32(responseData.Substring(0, 3));
if (responseCode == expectedCode) {
return true;
}
return false;
}
public override void SendData(string data) {
byte[] dataArray = Encoding.UTF8.GetBytes(data);
_socket.Send(dataArray, 0, dataArray.Length, SocketFlags.None);
}
}
SmtpConnectorWithSsl:
internal class SmtpConnectorWithSsl : SmtpConnectorBase {
private SslStream _sslStream = null;
private TcpClient _client = null;
public SmtpConnectorWithSsl(string smtpServerAddress, int port) : base(smtpServerAddress, port) {
TcpClient client = new TcpClient(smtpServerAddress, port);
_sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
// The server name must match the name on the server certificate.
try {
_sslStream.AuthenticateAsClient(smtpServerAddress);
} catch (AuthenticationException e) {
_sslStream = null;
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null) {
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
client.Close();
}
}
~SmtpConnectorWithSsl() {
try {
if (_sslStream != null) {
_sslStream.Close();
_sslStream.Dispose();
_sslStream = null;
}
} catch (Exception) {
;
}
try {
if (_client != null) {
_client.Close();
_client = null;
}
} catch (Exception) {
;
}
}
// The following method is invoked by the RemoteCertificateValidationDelegate.
private static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors) {
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
// Do not allow this client to communicate with unauthenticated servers.
return false;
}
public override bool CheckResponse(int expectedCode) {
if (_sslStream == null) {
return false;
}
var message = ReadMessageFromStream(_sslStream);
int responseCode = Convert.ToInt32(message.Substring(0, 3));
if (responseCode == expectedCode) {
return true;
}
return false;
}
public override void SendData(string data) {
byte[] messsage = Encoding.UTF8.GetBytes(data);
// Send hello message to the server.
_sslStream.Write(messsage);
_sslStream.Flush();
}
private string ReadMessageFromStream(SslStream stream) {
byte[] buffer = new byte[2048];
StringBuilder messageData = new StringBuilder();
int bytes = -1;
do {
bytes = stream.Read(buffer, 0, buffer.Length);
// Use Decoder class to convert from bytes to UTF8
// in case a character spans two buffers.
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
decoder.GetChars(buffer, 0, bytes, chars, 0);
messageData.Append(chars);
// Check for EOF.
if (messageData.ToString().IndexOf(EOF) != -1) {
break;
}
} while (bytes != 0);
return messageData.ToString();
}
}
You could open up the port (25) with a socket or TcpClient and see if it responds.
Open a socket connection to the smtp server on port 25 and see if you get anything. If not, no smtp server.
Here is a nice open source tool (does more than MX):
http://www.codeproject.com/KB/IP/DNS_NET_Resolver.aspx