SOCKS5中的UDP穿透

socket5是一种代理协议,如果防火墙禁止所有的计算机发送udp包,代理也没用。 
http隧道技术 是在防火墙禁止了udp的数据包,可以把数据封装在http包中,防火墙让看起来像http包的数据包通过,在防火墙外有专门的计算机把http包还原成udp包。



1. SOCKS5

1.1  简介

SOCKS5大家肯定很熟悉,是我们常用的代理协议的一种。它是Socks协议的第五版,相对于第四版增加了对身份验证,UDP,IPV6的支持。

一般的代理协议(比如Http代理)都是工作在应用层的,功能单一。而Socks代理协议旨在提供一种通用的代理服务,所以工作在应用层和传输层之间,只是传递传输层网络数据包(TCP/UDP),对于应用层的协议并不关心。

1.2 SOCKS5中的方法

SOCKS5在使用时是典型的CS模式的。SOCKS5服务可以分成两大功能,身份认证和代理服务。每个功能又支持不定的多种方法(Method)。

身份认证常用的方法是不需认证(No Authentication Required)和用户名/密码认证(Username/Password),多数应用软件包括我们监控CU都只支持这两种方法。

代理服务的模式有连接(Connect),绑定(Bind)和UDP穿透(UDP Associate)。OSP通过代理连接服务器的时候,是用的连接模式。通过代理服务器看码流的时候用的是UDP穿透模式。本案例只介绍一下UDP穿透模式的过程。

1.3 信令

SOCKS5的信令都是按位解释的。一般的信令表示如下:

          +----+----------+----------+

          |VER | NMETHODS | METHODS  |

          +----+----------+----------+

          | 1  |    1     | 1 to 255 |

           +----+----------+----------+

第一行是信令中该位置表示的含义。第二行是长度。

2 UDP穿透

UDP穿透(UDP Associate)是SOCKS5新加入的功能。

2.1 流程

    SOCKS5中的UDP穿透_第1张图片

图表 1 UDP 穿透过程

流程如上图。下面具体讲各个交互信令。

2.2 连接和认证

客户端TCP连接SOCKS5代理服务器的服务端口,并完成认证过程。不详述。

2.3 UDP穿透请求

客户端会用通过认证的这个TCP连接发送UDP穿透请求,信令格式如下:

    +----+-----+-------+------+----------+----------+

    |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |

    +----+-----+-------+------+----------+----------+

    | 1  |  1  | X'00' |  1   | Variable |    2     |

    +----+-----+-------+------+----------+----------+

其中各项:

o  VER    协议版本,对于SOCKS5都是 5。

      o  CMD      要请求的命令,UDP穿透填3。 其它的CONNECT是1,BIND是2。

      o  RSV    保留字段,填0。

      o  ATYP   地址类型,我们用IPV4,填1。域名的话填3,IPV6的话填4。

      o  DST.ADDR   IP地址。对于UDP穿透来说,好像没有什么意义,填0(也就是127.0.0.1)。

      o  DST.PORT  这个很重要,要填客户端想发送/接收UDP包的本地端口。后面在发送UDP包时代理服务器会检测收到的UDP包的源端口,只有和这里填入的端口号符合的包才会被处理。(CCProxy收到源端口错误的包会出bug,狂发包。)

Demo中的对应代码:

    char abyUdpAssociateBuf[1024] = { 0 };
 
    //sock5代理版本号,当然是5了。
    const int SOCK5_PROXY_VERSION = 0x05;
    const int CMD_UDP_ASSOCIATE = 0x03;
    const int RESERVED = 0;
    const int IP_TYPE = 0x01;   // ipv4
 
    int nAddr = inet_addr( m_strLocalIp );
    short nPort = htons( (short)m_nPortReq );
 
    abyUdpAssociateBuf[0] = SOCK5_PROXY_VERSION;
    abyUdpAssociateBuf[1] = CMD_UDP_ASSOCIATE;
    abyUdpAssociateBuf[2] = RESERVED;
    abyUdpAssociateBuf[3] = IP_TYPE;
    memcpy( &abyUdpAssociateBuf[4], &nAddr, 4 );
    memcpy( &abyUdpAssociateBuf[8], &nPort, 2 );
 
    if( SOCKET_ERROR == send( this->m_hProxyControl, abyUdpAssociateBuf, 10 , 0 ) )
    {
       this->ShowAssociateMsg( "Send associate msg fail!" );
       this->Release();
       return ;
    }

2.4 UDP穿透应答

代理服务器会回应客户端的请求,消息格式如下:

+----+-----+-------+------+----------+----------+

    |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |

    +----+-----+-------+------+----------+----------+

    | 1  |  1  | X'00' |  1   | Variable |    2     |

    +----+-----+-------+------+----------+----------+

其中:

o  VER    协议版本,对于SOCKS5都是 5。

      o  REP    代理服务器返回的结果,含义如下:

      X'00' succeeded

      X'01' general SOCKS server failure

      X'02' connection not allowed by ruleset

      X'03' Network unreachable

      X'04' Host unreachable

      X'05' Connection refused

      X'06' TTL expired

      X'07' Command not supported

      X'08' Address type not supported

      X'09' to X'FF' unassigned

      o  RSV    保留字段。

      o  ATYP   后面IP地址的格式,含义如下:

      IP V4 address: X'01'

DOMAINNAME: X'03'

      IP V6 address: X'04'

      o  BND.ADDR   此UDP穿透通道对应的代理服务器地址。

      o  BND.PORT    此UDP穿透通道对应的代理服务器端口。

至此,UDP穿透通道已经被建起来了,客户端只要按标准格式将UDP包发往上述地址端口,UDP包就会被代理服务器转发出去。

Demo中对应的代码:

 

   if( 10 != recv( this->m_hProxyControl, abyUdpAssociateBuf, sizeof(abyUdpAssociateBuf), 0 ) )
    {
       this->ShowAssociateMsg( "Receive reply of UDP Associate req fail!" );
       return;
    }
 
    // 校验返回值。
    const int SOCK5_PROXY_SUCCESS = 0;
    if ( SOCK5_PROXY_VERSION != abyUdpAssociateBuf[0]
       || SOCK5_PROXY_SUCCESS != abyUdpAssociateBuf[1]
       || IP_TYPE != abyUdpAssociateBuf[3] )
    {
       this->ShowAssociateMsg( "proxy error!" );
       return;
    }
 
    // 看服务器返回的地址。
    m_strIPProxyReply = inet_ntoa( *(IN_ADDR*)(&abyUdpAssociateBuf[4]) );
    m_sPortProxyReply = ntohs( *(short*)( &abyUdpAssociateBuf[8] ) );
 
    this->ShowAssociateMsg( "UDP Associate success!" );

2.5 UDP包发出

上面交互信令中都没有提到客户端想通过代理访问的远端服务器地址。UDP包的最终目的地是通过在原始UDP包数据前加一个包头来实现的。包头的格式如下:

    +----+------+------+----------+----------+----------+

    |RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |

    +----+------+------+----------+----------+----------+

    | 2  |  1   |  1   | Variable |    2     | Variable |

    +----+------+------+----------+----------+----------+

其中:

o  RSV  保留字段,填0。

       o  FRAG    当前分片序号,我们没有分片,填0。

       o  ATYP    地址类型,和前面的几个一样。IPV4填 1 。

       o  DST.ADDR  UDP包最终的目的地址。

       o  DST.PORT   UDP包最终的目的端口。

       o  DATA     原始的UDP包的数据。

按照上面格式发出的UDP包中的DATA部分会被代理服务器转发到包头中填入的最终目的地址。也就是说,我们用一路UDP穿透通道可以向不同的服务器发送数据。因为包头会被代理服务器去掉,所以远端服务器是不用知道客户端是否使用了代理。

Demo中对应的包头组装代码:

    const int BUF_SIZE = 1024;
    char abySentBuf[ BUF_SIZE ];
    char *pCursor = abySentBuf;
 
    *(short*)pCursor = 0;    // RSV  Reserved X'0000'
    pCursor += 2;
   
    *pCursor = 0; // Current fragment number
    pCursor++;
 
    *pCursor = 0x01;  // IP V4 address: X'01'
    pCursor ++;
 
    int nIp = inet_addr( m_strRemoteIp );
    *(int*)pCursor = nIp;    // desired destination address
    pCursor += 4;
 
    *(short*)pCursor = htons( m_nRemotePort );
    pCursor += 2;
 
    // 最后是我们的消息。
    strcpy( pCursor, this->m_strMsgSentToRemote );
    pCursor += this->m_strMsgSentToRemote.GetLength() + 1;
 
    int nDataLen = pCursor - abySentBuf;

2.6 包发入

远端服务器不用知道客户端是否使用了代理,它只要将需要回复的UDP包发送到它接收UDP包的源端口就行,在远端服务器看来表现和NAT表现一样。

但代理服务器不会直接转发发入的包,它会在原始数据包前面再封装一个包头然后发送给客户端。这个包头和上面的格式是一样的。客户端在处理之前,首先要去掉或跳过这个包头,才能得到服务器发送过来的原始数据。

3 实战分析

白话半天容易晕,直接上图,不解释。

SOCKS5中的UDP穿透_第2张图片

图表 2 监控系统中的码流代理UDP穿透示意图

 

4         容易出现的问题

4.1 链路的断链情况

在标准中说的很清楚,UDP通道和发送请求的TCP连接是拴在一根绳上的蚂蚱。如果这个TCP连接断开了,UDP通道也会跟着生效。反过来,如果因为某些原因导致UDP通道失效了(比如客户端发送了一个错误的UDP包,导致代理服务器停掉UDP通道),那么这个TCP连接也会被跟着中断掉。所以客户端程序必须要检测这个TCP连接的情况,如果发现断链了,就需要重新打洞。

4.2 每个TCP连接只能请求一路UDP通道,虽然标准中并没有此限制,但CCProxy就是这么做的。请求第二路通道会返回失败,此前打通的那路也会断掉。这样,因为监控客户端需要的码流通道非常多,现在每个客户端登录后要开157路UDP通道,对应157个TCP连接,对代理服务器的压力很大,估计支撑不了几个人同时登录。还有就是代理转发效率可能不高,像CCProxy在高码率下会严重丢包。

这个暂时还没有什么好办法解决。

5 附件

5.1 Demo 程序

在这里下载源码:

 http://download.csdn.net/source/2508913

5.2        Rfc文档

当然,标准文档直接找也可以。

http://download.csdn.net/source/2508935



你可能感兴趣的:(SOCKS5中的UDP穿透)