ESBasic 可复用的.NET类库(28) -- TCP代理服务器 TCPProxy

1.缘起:

    本文所描述的TCP代理服务器工作于网络协议层次中的应用层,位于传输层之上。只要是以TCP的方式为客户提供服务的(包括我们的HTTP服务器,HTTP底层走的仍然是TCP),我们都可以在真正的TCP服务器前面增加代理服务器。

  ESBasic 可复用的.NET类库(28) -- TCP代理服务器 TCPProxy_第1张图片

TCP代理服务器可以隐藏背后真正TCP服务器,如此便可以起到保护真正TCP服务器的作用。由于TCP代理服务器工作于应用层,所以,黑客对应用层以下级别的协议栈的攻击(比如TCP半连接攻击)就无法穿过TCP代理服务器,这样,即使TCP代理服务器挂了,我们真正的TCP服务器仍然可以正常运行。当然,如果黑客是针对应用层进行攻击的,这时,代理服务器就不起作用的。

当黑客攻击应用层以下级别的协议栈,我们可以在真正的TCP服务器前面部署N个代理服务器,并将它们分布于不同的地方,这样,当其中一个代理服务器因为遭遇攻击而挂掉时,受影响的只是连接到这台代理服务器的用户,而其它的代理服务器上的用户仍然是正常被服务的,就像下面这样:

  ESBasic 可复用的.NET类库(28) -- TCP代理服务器 TCPProxy_第2张图片

 

TCP代理服务器还有另外一个重要的作用,就是转址机。比如,我们的机房位于广州,而当北京的用户来访问服务时,网络延迟很大。我们可以在北京的机房部署一台代理服务器,并将该代理服务器与广州服务器之间的网络路由调节到最优。如此,北京的用户就可以通过这台代理服务器来快速地访问我们提供的服务了。对于转址机这项功能而言,有很多现有的软件可以做到,它们通常工作于网络协议层次中的IP层(即网络层),所以它们除了可以做TCP转址外,还可以做UDP的转址。

 

2.适用场合:

   综上所述,以下三种主要场景可以使用本文描述的TCP代理服务器:

(1)需要隐藏真正TCP服务器的位置。

(2)保护TCP服务器免受应用层以下级别的协议栈攻击。

(3)TCP转址机。

 

3.设计思想与实现

       TCP代理服务器工作于应用层,对于TCP连接的转发是全连接转发,即只有当客户端与代理服务器之间的TCP连接建立成功后,代理服务器才与真正的TCP服务器建立对应的连接。这种,客户端与Proxy之间的连接以及对应的ProxyTCP服务器之间的连接,称为一个连接对,使用ConnectionPair类进行封装:

    ///   <summary>
    
///  TCP连接对。
    
///   </summary>
     public   class   ConnectionPair  :IDisposable
    {
        
#region  Ctor
        
public  ConnectionPair( NetworkStream  client,  NetworkStream  proxy,  IPEndPoint  client_ipe,  int  proxyBufferSize,  int  clientBufferSize , int  _mappingPort)
        {
            
this .clientStream  =  client;
            
this .proxyStream  =  proxy;
            
this .clientIPE  =  client_ipe;
            
this .proxyBuffer  =   new   byte [proxyBufferSize];
            
this .clientBuffer  =   new   byte [clientBufferSize];
            
this .mappingPort  =  _mappingPort;
        } 
        
#endregion

        
#region  CreateTime
        
private   DateTime  createTime  =  DateTime.Now;
        
///   <summary>
        
///  连接对的创建时间。
        
///   </summary>
         public   DateTime  CreateTime
        {
            
get  {  return  createTime; }           
        } 
        
#endregion

        
#region  LastDataFromClientTime
        
private   DateTime  lastDataFromClientTime  =  DateTime.Now;
        
///   <summary>
        
///  从客户端接收到的最后一条消息的时间。
        
///   </summary>
         public   DateTime  LastDataFromClientTime
        {
            
get  {  return  lastDataFromClientTime; }            
        } 
        
#endregion

        
#region  LastDataFromServerTime
        
private   DateTime  lastDataFromServerTime  =  DateTime.Now;
        
///   <summary>
        
///  从服务器接收到的最后一条消息的时间。
        
///   </summary>
         public   DateTime  LastDataFromServerTime
        {
            
get  {  return  lastDataFromServerTime; }
        }
        
#endregion

        
#region  BytesFromServer
        
private   uint  bytesFromServer  =   0 ;
        
///   <summary>
        
///  转发的服务器发送给客户端的字节数。
        
///   </summary>
         public   uint  BytesFromServer
        {
            
get  {  return  bytesFromServer; }
            
set  
            {
                bytesFromServer 
=  value;
                
this .lastDataFromServerTime  =  DateTime.Now;
            }
        } 
        
#endregion

        
#region  BytesFromClient
        
private   uint  bytesFromClient  =   0 ;
        
///   <summary>
        
///  转发的客户端服发送给务器的字节数。
        
///   </summary>
         public   uint  BytesFromClient
        {
            
get  {  return  bytesFromClient; }
            
set  
            { 
                bytesFromClient 
=  value;
                
this .lastDataFromClientTime  =  DateTime.Now;
            }
        } 
        
#endregion         

        
#region  ClientBuffer
        
private   byte [] clientBuffer;
        
///   <summary>
        
///  用于转发客户端发送给真实服务器的消息的缓冲区
        
///   </summary>
         public   byte [] ClientBuffer
        {
            
get  {  return  clientBuffer; }
        }
        
#endregion

        
#region  ProxyBuffer
        
private   byte [] proxyBuffer;
        
///   <summary>
        
///  用于转发真实服务器发送给客户端的消息的缓冲区
        
///   </summary>
         public   byte [] ProxyBuffer
        {
            
get  {  return  proxyBuffer; }
        } 
        
#endregion

        
#region  ClientStream
        
private   NetworkStream  clientStream;
        
///   <summary>
        
///  客户端与代理服务器的连接。
        
///   </summary>
         public   NetworkStream  ClientStream
        {
            
get  {  return  clientStream; }           
        } 
        
#endregion

        
#region  ProxyStream
        
private   NetworkStream  proxyStream;
        
///   <summary>
        
///  代理服务器与真实服务器的连接。
        
///   </summary>
         public   NetworkStream  ProxyStream
        {
            
get  {  return  proxyStream; }           
        } 
        
#endregion

        
#region  MappingPort
        
private   int  mappingPort  =   0 ;
        
///   <summary>
        
///  当前ProxyStream与服务器通信的端口。
        
///   </summary>
         public   int  MappingPort
        {
            
get  {  return  mappingPort; }            
        }
        
#endregion

        
#region  ClientIPE
        
private   IPEndPoint  clientIPE;
        
///   <summary>
        
///  客户端的IPE
        
///   </summary>
         public   IPEndPoint  ClientIPE
        {
            
get  {  return  clientIPE; }           
        } 
        
#endregion

        
#region  Dispose
        
public   void  Dispose()
        {
            
try
            {
                
this .clientStream.Close();               
                
this .clientStream.Dispose();
                
this .proxyStream.Close();
                
this .proxyStream.Dispose();
            }
            
catch  { }
        } 
        
#endregion
    }

 

 

     在实现TCP代理服务器时,遵循以下几点原则:

(1)当客户端与代理服务器建立TCP连接成功时,代理服务器立即与TCP服务器建立连接,并将它们作为一个连接对管理起来。

(2)当连接对中的任何一个连接断开时,代理服务器都关闭另外一个连接,并释放该连接对所持有的任何资源。

(3)当接收到来自客户端的任何数据时,都原封不动地转发给TCP服务器;当接收到来自TCP服务器的任何数据时,都原封不动地转发给对应的客户端。

(4)通过客户端的端点地址IPEndPoint来识别不同的连接对。

 

4. 使用时的注意事项

(1)可以根据具体应用中消息的大小来设置缓冲区的大小,即ClientBufferSizeProxyBufferSize属性,以使数据转发的效率更高。

(2)注意WriteTimeoutInMSecs属性,当某个客户的网络非常差时,如果Proxy向该客户发送数据的时间超过WriteTimeoutInMSecs毫秒,则代理服务器会断开该客户的连接,并释放连接对。

(3)可以在代理服务器上通过CloseConnection方法来主动关闭某个连接对。

 

5.扩展

使用TCPProxy类,我写了个简单的TCP代理服务器应用程序,对于简单的场景可以直接拿来使用。该程序运行的截图如下:

 

  ESBasic 可复用的.NET类库(28) -- TCP代理服务器 TCPProxy_第3张图片

     使用时,只需要修改一下TCPProxyServer.exe.config中对应的配置即可。

     可以从这里下载TCP代理服务器

 

注: ESBasic已经开源,点击这里下载源码。  
    
ESBasic开源前言

 

 

你可能感兴趣的:(proxy)