SOCKS 5协议详解

来源:互联网 发布:凌凯短信软件 编辑:程序博客网 时间:2024/06/16 04:04

文章摘要:    
    该文档描述了一个应用层的用于穿越IP网络防火墙的协议。这种穿越的安全性是高度依赖于正规的认证和正规执行方法提供的有效封装,以及在SOCKS客户端和SOCKS服务端所选择的安全性,还有管理员对认证方法选项所作的小心周密的考虑。      
                     
   
  --------------------------------------------------------------------------------  
     
  正文:          
   
  SOCKS   5协议详解       
   
    笔者在实际学习中,由于在有些软件用到了socks5(如oicq,icq等),对其原理不甚了解,相信很多朋友对其也不是很了解,于是仔细研读了一下rfc1928,觉得有必要译出来供大家参考。  
   
  1.介绍:  
   
    防火墙的使用,有效的隔离了机构的内部网络和外部网络,这种类型的Internet架构变得越来越流行。这些防火墙系统大都充当着网络之间的应用层网关的角色,通常提供经过控制的Telnet,FTP,和SMTP访问。为了推动全球信息的交流,更多的新的应用层协议的推出。这就有必要提供一个总的架构使这些协议能够更明显和更安全的穿过防火墙。也就有必要在实际上为它们穿过防火墙提供一个更强的认证机制。这种需要源于客户机-服务器联系在不同组织网络之间的实现,而这种联系需要被控制和是很大程度上被认证的。  
    该协议被描述为用来提供在TCP和UDP域下为客户机-服务器应用程序便利和安全的穿过防火墙的一个架构。该协议在概念上被描述为一个介于应用层和传输层之间的"隔离层",但是这类服务并不提供网络层网关服务,如ICMP报文的传输。  
   
  2.现状:  
   
    SOCKS   4为基于TCP的客户机-服务器应用程序提供了一种不安全的穿越防火墙的机制,包括TELNET,FTP和当前最流行的信息发现协议如HTTP,WAIS和GOPHER.  
    新协议为了包括UDP扩展了SOCKS   4,为了包括对总体上更强的认证机制的支持扩展了协议架构,为了包括域名和IPv6地址的支持扩展了地址集。  
    SOCKS协议执行最具代表性的是包括了在SOCKS库中利用适当的封装程序来对基于TCP的客户程序进行重编译和重链结。  
   
  注意:  
    除非特别提及,封装在包格式中的十进制数表示的是通讯域的长度(用八位组octect表示)。一个给定的八位组必须具有指定的值,格式X'hh'被用来表示在该域中单个八位组的值。当单词"变量Variable"被使用时,它指出了通讯域拥有一个可变长度,这个可变长度要么由一个联合的(一个或两个八位组)长度域定义,要么由一个数据类型域所定义。  
   
  3.基于TCP客户机的程序  
   
    当一台基于TCP的客户机希望和目标主机建立连接时,而这台目标主机只有经过防火墙才能到达(这种情况?一直持续到?它被执行时),它就必须在 SOCKS服务器端的适当的SOCKS端口打开一个TCP连结。SOCKS服务按常例来说定位于TCP端口1080。如果连接请求成功,客户机为即将使用的认证方式进行一种协商,对所选的方式进行认证,然后发送一个转发请求。SOCKS服务器对该请求进行评估,并且决定是否建立所请求转发的连接。  
    客户机连接到服务器,发送一个版本标识/方法选择报文:  
   
    +----+----------+----------+  
    |VER   |   NMETHODS   |   METHODS   |  
    +----+----------+----------+  
    |   1 |     1  |   1   to   255   |  
    +----+----------+----------+  
   
    VER(版本)在这个协议版本中被设置为X'05'。NMETHODS(方法选择)中包含在METHODS(方法)中出现的方法标识八位组的数目。  
    服务器从METHODS给出的方法中选出一种,发送一个METHOD   selection(方法选择)报文:  
   
    +----+--------+  
    |VER   |   METHOD   |  
    +----+--------+  
    |   1 |    1  |  
    +----+--------+  
   
    如果所选择的METHOD的值是X'FF',则客户机所列出的方法是没有可以被接受的,客户机就必须关闭连接。  
   
  当前被定义的METHOD的值有:  
    >>   X'00'   无验证需求  
    >>   X'01'   通用安全服务应用程序接口(GSSAPI)  
    >>   X'02'   用户名/密码(USERNAME/PASSWORD)  
    >>   X'03'   至   X'7F'   IANA   分配(IANA   ASSIGNED)    
    >>   X'80'   至   X'FE'   私人方法保留(RESERVED   FOR   PRIVATE   METHODS)  
    >>   X'FF'   无可接受方法(NO   ACCEPTABLE   METHODS)  
  ***IANA是负责全球INTERNET上的IP地址进行编号分配的机构(译者著)***  
    于是客户机和服务器进入方法细节的子商议。方法选择子商议另外描述于独立的文档中。  
    欲得到该协议新的METHOD支持的开发者可以和IANA联系以求得到METHOD号。已分配号码的文档需要参考METHOD号码的当前列表和它们的通讯协议。  
    如果想顺利的执行则必须支持GSSAPI和支持用户名/密码(USERNAME/PASSWORD)认证方法。  
   
  4.需求  
   
    一旦方法选择子商议结束,客户机就发送请求细节。如果商议方法包括了完整性检查的目的和/或机密性封装,则请求必然被封在方法选择的封装中。  
   
  SOCKS请求如下表所示:  
   
    +----+-----+-------+------+----------+----------+  
    |VER   |   CMD   | RSV |   ATYP   |   DST.ADDR   |   DST.PORT   |  
    +----+-----+-------+------+----------+----------+  
    |   1 |    1   |   X'00'   |    1 |   Variable   |     2  |  
    +----+-----+-------+------+----------+----------+  
   
  其中:  
  o   VER   protocol   version:X'05'  
  o   CMD  
   o   CONNECT   X'01'  
   o   BIND   X'02'  
   o   UDP   ASSOCIATE   X'03'  
  o   RSV   RESERVED  
  o   ATYP   address   type   of   following   address  
   o   IP   V4   address:   X'01'  
   o   DOMAINNAME:   X'03'  
   o   IP   V6   address:   X'04'  
  o   DST.ADDR   desired   destination   address  
  o   DST.PORT   desired   destination   port   in   network   octet   order  
   
  5.地址  
   
    在地址域(DST.ADDR,BND.ADDR)中,ATYP域详细说明了包含在该域内部的地址类型:  
      o   X'01'  
   
    该地址是IPv4地址,长4个八位组。  
      o   X'03'  
   
    该地址包含一个完全的域名。第一个八位组包含了后面名称的八位组的数目,没有中止的空八位组。  
      o   X'04'  
   
    该地址是IPv6地址,长16个八位组。  
   
  6.回应  
   
    到SOCKS服务器的连接一经建立,客户机即发送SOCKS请求信息,并且完成认证商议。服务器评估请求,返回一个回应如下表所示:  
   
   
    +----+-----+-------+------+----------+----------+  
    |VER   |   REP   | RSV |   ATYP   |   BND.ADDR   |   BND.PORT   |  
    +----+-----+-------+------+----------+----------+  
    |   1 | 1 |   X'00'   | 1    |   Variable   |     2  |  
    +----+-----+-------+------+----------+----------+  
   
  其中:  
   
  o   VER   protocol   version:   X'05'  
  o   REP   Reply   field:  
    o   X'00'   succeeded  
    o   X'01'   general   SOCKS   server   failure  
    o   X'02'   connection   not   allowed   by   ruleset  
    o   X'03'   Network   unreachable  
    o   X'04'   Host   unreachable  
    o   X'05'   Connection   refused  
    o   X'06'   TTL   expired  
    o   X'07'   Command   not   supported  
    o   X'08'   Address   type   not   supported  
    o   X'09'   to   X'FF'   unassigned  
  o   RSV   RESERVED  
  o   ATYP   address   type   of   following   address  
    o   IP   V4   address:   X'01'  
    o   DOMAINNAME:   X'03'  
    o   IP   V6   address:   X'04'  
  o   BND.ADDR   server   bound   address  
  o   BND.PORT   server   bound   port   in   network   octet   order  
  标志RESERVED(RSV)的地方必须设置为X'00'。  
   
    如果被选中的方法包括有认证目的封装,完整性和/或机密性的检查,则回应就被封装在方法选择的封装套中。  
   
  CONNECT  
   
    在CONNECT的回应中,BND.PORT包括了服务器分配的连接到目标主机的端口号,同时BND.ADDR包含了关联的IP地址。此处所提供的 BND.ADDR通常情况不同于客户机连接到SOCKS服务器所用的IP地址,因为这些服务器提供的经常都是多址的(muti-homed)。都期望 SOCKS主机能使用DST.ADDR和DST.PORT,连接请求评估中的客户端源地址和端口。  
   
  BIND  
   
    BIND请求被用在那些需要客户机接受到服务器连接的协议中。FTP就是一个众所周知的例子,它通过使用命令和状态报告建立最基本的客户机-服务器连接,按照需要使用服务器-客户端连接来传输数据。(例如:ls,get,put)  
  都期望在使用应用协议的客户端在使用CONNECT建立首次连接之后仅仅使用BIND请求建立第二次连接。都期望SOCKS主机在评估BIND请求时能够使用DST.ADDR和DST.PORT。  
    有两次应答都是在BIND操作期间从SOCKS服务器发送到客户端的。第一次是发送在服务器创建和绑定一个新的socket之后。BIND.PORT 域包含了SOCKS主机分配和侦听一个接入连接的端口号。BND.ADDR域包含了关联的IP地址。  客户端具有代表性的是使用这些信息来通报应用程序连接到指定地址的服务器。第二次应答只是发生在预期的接入连接成功或者失败之后。在第二次应答中,BND.PORT和BND.ADDR域包含了欲连接主机的地址和端口号。  
   
  UDP   ASSOCIATE(连接?)  
   
    UDP   连接请求用来建立一个在UDP延迟过程中操作UDP数据报的连接。DST.ADDR和DST.PORT域包含了客户机期望在这个连接上用来发送UDP数据报的地址和端口。服务器可以利用该信息来限制至这个连接的访问。如果客户端在UDP连接时不持有信息,则客户端必须使用一个全零的端口号和地址。  
   
    当一个含有UDP连接请求到达的TCP连接中断时,UDP连接中断。  
   
    在UDP连接请求的回应中,BND.PORT和BND.ADDR域指明了客户端需要被发送UDP请求消息的端口号/地址。  
   
  回应过程  
   
    当一个回应(REP值非X'00')指明失败时,SOCKS主机必须在发送后马上中断该TCP连接。该过程时间必须为在侦测到引起失败的原因后不超过10秒。  
    如果回应代码(REP值为X'00')时,则标志成功,请求或是BIND或是CONNECT,客户机现在就可以传送数据了。如果所选择的认证方法支持完整性、认证机制和/或机密性的封装,则数据被方法选择封装包来进行封装。类似,当数据从客户机到达SOCKS主机时,主机必须使用恰当的认证方法来封装数据。  
   
  7.基于UDP客户机的程序  
传透代理服务器的编程     
        
  正文  
  在网络程序设计过程中,我们经常要与各种类型的代理服务器打交道,比如在企业内部网通过代理去访问Internet网上的服务器等等,一般代理服务器支持几种常见的代理协议标准,如Socks4,Socks5,Http代理,其中Socks5需要用户验证,代理相对复杂。我在查阅RFC文档和相关资料后,特总结一些TCP协议穿透代理服务器的程序片断,希望对大家有所帮助。  
   
  //使用到的结构  
  struct   sock4req1  
  {  
     char   VN;  
   char   CD;  
   unsigned   short   Port;  
   unsigned   long   IPAddr;  
   char   other[1];  
  };  
   
  struct   sock4ans1  
  {  
   char   VN;  
   char   CD;  
  };  
   
  struct   sock5req1  
  {  
   char   Ver;  
   char   nMethods;  
   char   Methods[255];  
  };  
   
  struct   sock5ans1  
  {  
   char   Ver;  
   char   Method;  
  };  
   
  struct   sock5req2  
  {  
   char   Ver;  
   char   Cmd;  
   char   Rsv;  
   char   Atyp;  
   char   other[1];  
  };  
   
  struct   sock5ans2  
  {  
   char   Ver;  
   char   Rep;  
   char   Rsv;  
   char   Atyp;  
   char   other[1];  
  };  
   
  struct   authreq  
  {  
   char   Ver;  
   char   Ulen;  
   char   Name[255];  
   char   PLen;  
   char   Pass[255];  
  };  
   
  struct   authans  
  {  
   char   Ver;  
   char   Status;  
  };  
   
  //通过Socks4方式代理  
  if(   !ClientSock.Connect(   g_ProxyInfo.m_strProxyIP,g_ProxyInfo.m_nProxyPort)   )  
  {  
    m_sError   =   _T("不能连接到代理服务器!");  
    ClientSock.Close();  
    return   FALSE;  
  }  
  char   buff[100];  
  memset(buff,0,100);  
  struct   sock4req1   *m_proxyreq;  
  m_proxyreq   =   (struct   sock4req1   *)buff;  
  m_proxyreq->VN   =   4;  
  m_proxyreq->CD   =   1;  
  m_proxyreq->Port   =   ntohs(GetPort());  
  m_proxyreq->IPAddr   =   inet_addr(GetServerHostName());  
  ClientSock.Send(buff,9);  
  struct   sock4ans1   *m_proxyans;  
  m_proxyans   =   (struct   sock4ans1   *)buff;  
  memset(buff,0,100);  
  ClientSock.Receive(buff,100);  
  if(m_proxyans->VN   !=   0   ||   m_proxyans->CD   !=   90)  
  {  
   m_sError   =   _T("通过代理连接主站不成功!");  
   ClientSock.Close();  
   return   FALSE;  
  }  
   
   
   
   
  //通过Socks5方式代理  
  if(   !ClientSock.Connect(   g_ProxyInfo.m_strProxyIP,g_ProxyInfo.m_nProxyPort)   )  
  {  
   m_sError   =   _T("不能连接到代理服务器!");  
   ClientSock.Close();  
   return   FALSE;  
  }  
  char   buff[600];  
  struct   sock5req1   *m_proxyreq1;  
  m_proxyreq1   =   (struct   sock5req1   *)buff;  
  m_proxyreq1->Ver   =   5;  
  m_proxyreq1->nMethods   =   2;  
  m_proxyreq1->Methods[0]   =   0;  
  m_proxyreq1->Methods[1]   =   2;  
  ClientSock.Send(buff,4);  
  struct   sock5ans1   *m_proxyans1;  
  m_proxyans1   =   (struct   sock5ans1   *)buff;  
  memset(buff,0,600);  
  ClientSock.Receive(buff,600);  
  if(m_proxyans1->Ver   !=   5   ||   (m_proxyans1->Method!=0   &&   m_proxyans1->Method!=2))  
  {  
   m_sError   =   _T("通过代理连接主站不成功!");  
   ClientSock.Close();  
   return   FALSE;  
  }  
  if(m_proxyans1->Method   ==   2)  
  {  
   int   nUserLen   =   strlen(g_ProxyInfo.m_strProxyUser);  
   int   nPassLen   =   strlen(g_ProxyInfo.m_strProxyPass);  
   struct   authreq   *m_authreq;  
   m_authreq   =   (struct   authreq   *)buff;  
   m_authreq->Ver   =   1;  
   m_authreq->Ulen   =   nUserLen;  
   strcpy(m_authreq->Name,g_ProxyInfo.m_strProxyUser);  
   m_authreq->PLen   =   nPassLen;  
   strcpy(m_authreq->Pass,g_ProxyInfo.m_strProxyPass);  
   ClientSock.Send(buff,513);  
   struct   authans   *m_authans;  
   m_authans   =   (struct   authans   *)buff;  
   memset(buff,0,600);  
   ClientSock.Receive(buff,600);  
   if(m_authans->Ver   !=   1   ||   m_authans->Status   !=   0)  
   {  
    m_sError   =   _T("代理服务器用户验证不成功!");  
    ClientSock.Close();  
   return   FALSE;  
   }  
  }  
  struct   sock5req2   *m_proxyreq2;  
  m_proxyreq2   =   (struct   sock5req2   *)buff;  
  m_proxyreq2->Ver   =   5;  
  m_proxyreq2->Cmd   =   1;  
  m_proxyreq2->Rsv   =   0;  
  m_proxyreq2->Atyp   =   1;  
  unsigned   long   tmpLong   =   inet_addr(GetServerHostName());  
  unsigned   short   port   =   ntohs(GetPort());  
  memcpy(m_proxyreq2->other,&tmpLong,4);  
  memcpy(m_proxyreq2->other+4,&port,2);  
  ClientSock.Send(buff,sizeof(struct   sock5req2)+5);  
  struct   sock5ans2   *m_proxyans2;  
  memset(buff,0,600);  
  m_proxyans2   =   (struct   sock5ans2   *)buff;  
  ClientSock.Receive(buff,600);  
  if(m_proxyans2->Ver   !=   5   ||   m_proxyans2->Rep   !=   0)  
  {  
   m_sError   =   _T("通过代理连接主站不成功!");  
   ClientSock.Close();  
   return   FALSE;  
  }  
   
   
   
   
  //通过HTTP方式代理  
  if(   !ClientSock.Connect(   g_ProxyInfo.m_strProxyIP,g_ProxyInfo.m_nProxyPort)   )  
  {  
   m_sError   =   _T("不能连接到代理服务器!");  
   ClientSock.Close();  
   return   FALSE;  
  }  
  char   buff[600];  
  sprintf(   buff,   "%s%s:%d%s","CONNECT   ",GetServerHostName(),GetPort(),"   HTTP/1.1/r/nUser-Agent:   MyApp/0.1/r/n/r/n");  
  ClientSock.Send(buff,strlen(buff));   //发送请求  
  memset(buff,0,600);  
  ClientSock.Receive(buff,600);  
  if(strstr(buff,   "HTTP/1.0   200   Connection   established")   ==   NULL)   //连接不成功  
  {  
   m_sError   =   _T("通过代理连接主站不成功!");  
   ClientSock.Close();  
   return   FALSE;  
  }  
   
   
  我们一般先与代理服务器连通,然后向代理服务器发送代理验证的用户名和密码(如果需要,如Socks5代理),验证成功后,再向代理服务器发送需要连接的目的地址和端口。以上代码仅用于TCP连接,如果在内部网侦听或通过UDP协议发送信息,可查阅RFC1829等文档资料。  

原创粉丝点击