title: TCP Port forwarding with Async Sockets
body: <p>I am trying to implement a TCP forwarder in C#. Specifically the application:</p>
  
  <ol>
  <li><strong>Listens</strong> to a TCP port and wait for a client,</li>
  <li>When a client is connected, <strong>connects</strong> to a remote host,</li>
  <li><strong>Waits for incoming data</strong> on the both connections and <strong>exchange the data</strong> between two endpoints (acts as an proxy),</li>
  <li><strong>Closes</strong> one connection when the other one get closed by an endpoint.</li>
  </ol>
  
  <p>I have adapted <a href="http://blog.brunogarcia.com/2012/10/simple-tcp-forwarder-in-c.html" rel="nofollow">Simple TCP Forwader</a> (by Garcia) to forward a range of ports so that</p>
  
  <pre><code>TCPForwarder.exe 10.1.1.1 192.168.1.100 1000 1100 2000
  </code></pre>
  
  <p>will forward any packets received on 10.1.1.1 on port 1000-1100 to remote host 192.168.1.100 port 2000-2100. I have employed this to expose a FTP server that is behind a NAT.</p>
  
  <p>By running the above command, the client is able to connect to the FTP server and the following pattern in outputted to the console which is expected (refer to code):</p>
  
  <pre><code>0 StartReceive: BeginReceive
  1 StartReceive: BeginReceive
  1 OnDataReceive: EndReceive
  1 OnDataReceive: BeginReceive
  1 OnDataReceive: EndReceive
  1 OnDataReceive: Close (0 read)
  0 OnDataReceive: EndReceive
  0 OnDataReceive: Close (exception)
  </code></pre>
  
  <p>But after successfully connecting for several times (pressing F5 in Filezilla), no further response is received from TCPForwarder (and FTP Server). </p>
  
  <p>There seems to be two problems with my implementation that I cannot debug:</p>
  
  <ol>
  <li><p>In that situation, the <strong>BeginReceive</strong> in <strong>StartReceive</strong> method gets called, but no data is received from the FTP server. I don't think that could be FTP server issue (it's a ProFTPD server) as it is a well-known FTP server.</p></li>
  <li><p>Every time a connection is made and closed, the number of threads increases by 1. I don't think garbage collection would fix that. Number of threads is consistently increasing and forcing garabage collector to run doesn't decrease that either. I think there is some leak in my code that is also causing issue #1.</p></li>
  </ol>
  
  <p><strong>Edit:</strong></p>
  
  <ul>
  <li><p>Restarting the FTP server didn't fix the problem, so there is definitely a bug in the TCPForwarder. </p></li>
  <li><p>Some issues pointed out by @jgauffin is fixed in the below code.</p></li>
  </ul>
  
  <p>Here is the full code:</p>
  
  <pre><code>using System;
  using System.Net;
  using System.Net.Sockets;
  using System.Collections.Generic;
  using System.Threading;
  
  namespace TCPForwarder
  {
      class Program
      {
          private class State
          {
              public int ID { get; private set; } // for debugging purposes
              public Socket SourceSocket { get; private set; }
              public Socket DestinationSocket { get; private set; }
              public byte[] Buffer { get; private set; }
              public State(int id, Socket source, Socket destination)
              {
                  ID = id;
                  SourceSocket = source;
                  DestinationSocket = destination;
                  Buffer = new byte[8192];
              }
          }
  
          public class TcpForwarder
          {
              public void Start(IPEndPoint local, IPEndPoint remote)
              {
                  Socket MainSocket;
                  try
                  {
                      MainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                      MainSocket.Bind(local);
                      MainSocket.Listen(10);
                  }
                  catch (Exception exp)
                  {
                      Console.WriteLine("Error on listening to " + local.Port + ": " + exp.Message);
                      return;
                  }
  
                  while (true)
                  {
                      // Accept a new client
                      var socketSrc = MainSocket.Accept();
                      var socketDest = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  
                      try
                      {
                          // Connect to the endpoint
                          socketDest.Connect(remote);
                      }
                      catch
                      {
                          socketSrc.Shutdown(SocketShutdown.Both);
                          socketSrc.Close();
                          Console.WriteLine("Exception in connecting to remote host");
                          continue;
                      }
  
                      // Wait for data sent from client and forward it to the endpoint
                      StartReceive(0, socketSrc, socketDest);
  
                      // Also, wait for data sent from endpoint and forward it to the client
                      StartReceive(1, socketDest, socketSrc);
                  }
              }
  
              private static void StartReceive(int id, Socket src, Socket dest)
              {
                  var state = new State(id, src, dest);
  
                  Console.WriteLine("{0} StartReceive: BeginReceive", id);
                  try
                  {
                      src.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
                  }
                  catch
                  {
                      Console.WriteLine("{0} Exception in StartReceive: BeginReceive", id);
                  }
              }
  
              private static void OnDataReceive(IAsyncResult result)
              {
                  State state = null;
                  try
                  {
                      state = (State)result.AsyncState;
  
                      Console.WriteLine("{0} OnDataReceive: EndReceive", state.ID);
                      var bytesRead = state.SourceSocket.EndReceive(result);
                      if (bytesRead &gt; 0)
                      {
                          state.DestinationSocket.Send(state.Buffer, bytesRead, SocketFlags.None);
  
                          Console.WriteLine("{0} OnDataReceive: BeginReceive", state.ID);
                          state.SourceSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
                      }
                      else
                      {
                          Console.WriteLine("{0} OnDataReceive: Close (0 read)", state.ID);
                          state.SourceSocket.Shutdown(SocketShutdown.Both);
                          state.DestinationSocket.Shutdown(SocketShutdown.Both);
                          state.DestinationSocket.Close();
                          state.SourceSocket.Close();
                      }
                  }
                  catch
                  {
                      if (state!=null)
                      {
                          Console.WriteLine("{0} OnDataReceive: Close (exception)", state.ID);
                          state.SourceSocket.Shutdown(SocketShutdown.Both);
                          state.DestinationSocket.Shutdown(SocketShutdown.Both);
                          state.DestinationSocket.Close();
                          state.SourceSocket.Close();
                      }
                  }
              }
          }
  
          static void Main(string[] args)
          {
              List&lt;Socket&gt; sockets = new List&lt;Socket&gt;();
  
              int srcPortStart = int.Parse(args[2]);
              int srcPortEnd = int.Parse(args[3]);
              int destPortStart = int.Parse(args[4]);
  
              List&lt;Thread&gt; threads = new List&lt;Thread&gt;();
              for (int i = 0; i &lt; srcPortEnd - srcPortStart + 1; i++)
              {
                  int srcPort = srcPortStart + i;
                  int destPort = destPortStart + i;
  
                  TcpForwarder tcpForwarder = new TcpForwarder();
  
                  Thread t = new Thread(new ThreadStart(() =&gt; tcpForwarder.Start(
                      new IPEndPoint(IPAddress.Parse(args[0]), srcPort),
                      new IPEndPoint(IPAddress.Parse(args[1]), destPort))));
                  t.Start();
  
                  threads.Add(t);
              }
  
              foreach (var t in threads)
              {
                  t.Join();
              }
              Console.WriteLine("All threads are closed");
          }
      }
  }
  </code></pre>