1.缘起:
本文所描述的TCP代理服务器工作于网络协议层次中的应用层,位于传输层之上。只要是以TCP的方式为客户提供服务的(包括我们的HTTP服务器,HTTP底层走的仍然是TCP),我们都可以在真正的TCP服务器前面增加代理服务器。
TCP代理服务器可以隐藏背后真正TCP服务器,如此便可以起到保护真正TCP服务器的作用。由于TCP代理服务器工作于应用层,所以,黑客对应用层以下级别的协议栈的攻击(比如TCP半连接攻击)就无法穿过TCP代理服务器,这样,即使TCP代理服务器挂了,我们真正的TCP服务器仍然可以正常运行。当然,如果黑客是针对应用层进行攻击的,这时,代理服务器就不起作用的。
当黑客攻击应用层以下级别的协议栈,我们可以在真正的TCP服务器前面部署N个代理服务器,并将它们分布于不同的地方,这样,当其中一个代理服务器因为遭遇攻击而挂掉时,受影响的只是连接到这台代理服务器的用户,而其它的代理服务器上的用户仍然是正常被服务的,就像下面这样:
TCP代理服务器还有另外一个重要的作用,就是转址机。比如,我们的机房位于广州,而当北京的用户来访问服务时,网络延迟很大。我们可以在北京的机房部署一台代理服务器,并将该代理服务器与广州服务器之间的网络路由调节到最优。如此,北京的用户就可以通过这台代理服务器来快速地访问我们提供的服务了。对于转址机这项功能而言,有很多现有的软件可以做到,它们通常工作于网络协议层次中的IP层(即网络层),所以它们除了可以做TCP转址外,还可以做UDP的转址。
2.适用场合:
综上所述,以下三种主要场景可以使用本文描述的TCP代理服务器:
(1)需要隐藏真正TCP服务器的位置。
(2)保护TCP服务器免受应用层以下级别的协议栈攻击。
(3)TCP转址机。
3.设计思想与实现
TCP代理服务器工作于应用层,对于TCP连接的转发是全连接转发,即只有当客户端与代理服务器之间的TCP连接建立成功后,代理服务器才与真正的TCP服务器建立对应的连接。这种,客户端与Proxy之间的连接以及对应的Proxy与TCP服务器之间的连接,称为一个连接对,使用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)可以根据具体应用中消息的大小来设置缓冲区的大小,即ClientBufferSize和ProxyBufferSize属性,以使数据转发的效率更高。
(2)注意WriteTimeoutInMSecs属性,当某个客户的网络非常差时,如果Proxy向该客户发送数据的时间超过WriteTimeoutInMSecs毫秒,则代理服务器会断开该客户的连接,并释放连接对。
(3)可以在代理服务器上通过CloseConnection方法来主动关闭某个连接对。
5.扩展
使用TCPProxy类,我写了个简单的TCP代理服务器应用程序,对于简单的场景可以直接拿来使用。该程序运行的截图如下:
使用时,只需要修改一下TCPProxyServer.exe.config中对应的配置即可。
可以从这里下载TCP代理服务器。
注: ESBasic已经开源,点击这里下载源码。
ESBasic开源前言