实现Sock5代理

有很多公司不能直接和Internet相连,必须通过代理和www连接,浏览、下载资料。
代理服务器支持的协议也有所不同,有支持Sock、HTTP代理的这样我们做的客户端软件就需要支持这些代理,使用户能够通过这些代理透过防火墙和外网相连,一般Sock分为Sock4和Sock5,这里我们只实现Sock5协议。
RFC1928描述了Socks协议的细节,告诉我们客户程序如何同Socks代理协商,取得透过该协议对外传输的途径。英文的URL为: http://www.ietf.org/rfc/rfc1928.txt,建议先了解以上链接内容后在阅读下文。另外给大家推荐学习协议的好地方: http://www.cnpaf.com中国协议分析网,有很多好东东,可以去看看。
下面说明一下实现的方法。
代理的实现方法实际就是Socket编程,只要通讯时遵循它们的协议就可以。首先理解它们的协议是关键。
示意图:

1.1    Sock5协议实现

1.1.1        Sock5协议内容

1.  客户端首先要与代理服务器连接,连接后要向代理发送版本号、认证方法、方法选择格式如下:
版本号(1字节) |  供选择的认证方法(1字节)  |  方法序列(1-255个字节长度)
如果你支持的版本为SOCK5那么版本号就为0x05;可供选择的方法是指你的协议支持几种认证方式,因为我们实现只支持一种所以就填0x01,如果你是两种就写0x02;认证方法序列包括(0x00为不需认证、0x02为需要用户名和密码认证,通常是这两种,如果想了解更多请参看Rfc1928)。
这样组合你的报文应该为:0x05 0x01 0x00
如果需要认证那么为:0x05 0x01 0x02
2.  代理接收到客户端的请求,会向客户端返回信息,格式为:
版本号 | 服务器选定的方法
如果服务器支持验证方式,返回的报文为:
0x05 0x02
3.  接下来根据服务器的验证方式,发送验证信息了,报文格式为:
0x01 | 用户名长度(1字节)| 用户名(长度根据用户名长度域指定) | 口令长度(1字节) | 口令(长度由口令长度域指定)
4.  服务器接收信息后进行验证,返回如下格式:
0x01 | 验证结果标志
验证结果标志:0x00表示验证成功,其他值均为错误码

1.1.2        Sock5协议实现

根据上面所述的步骤和协议内容,就可以实现SOCK5代理了。下面我给出我写的实现SOCKT5代理的函数代码和注释,供参考(delphi6+win2K+sp4调试通过,代理服务器用的是CCProxy6.0)
function TBZSock5ProxyApi.SetProxy(ASocket: Pointer): Integer;
var
  proxyUser, proxyPwd: String;
  bIsValid: Boolean;
  sock: 
^ TSocket;
  sockServer: TSockAddrIn;
  command: array [
0 .. 9 ] of Byte;
  re, len, ulen, plen: Integer;
//   buffer: PByte;
  buffer: array [ 0 .. 1023 ] of Byte;
begin
  sock :
=  ASocket;
  
if  FProxyParam.GetServer  =   ''  then
  begin
    Result :
=   0 ;
    Exit;
  end
  
else
  begin
    Result :
=   0 ;
    sock
^  : =  socket(AF_INET, SOCK_STREAM,  0 );
    
if  sock ^   =  INVALID_SOCKET then
    begin
      Result :
=   1 ;
      Exit;
    end;
 
    sockServer.sin_family :
=  AF_INET;
    sockServer.sin_port :
=  htons(FProxyParam.GetPort);     // 将整形数变为网络字节流
    sockServer.sin_addr.S_addr : =  inet_addr(PChar(FProxyParam.GetServer));
 
    
// 连接远程主机
     if  WinSock.connect(sock ^ , sockServer, SizeOf(sockServer))  <>   0  then
    begin
      Result :
=   1 ;
      Exit;
    end;
 
    bIsValid :
=  FProxyParam.GetProxyValid;
 
    
// 发送SOCK5协议指令
    FillChar(command,  10 0 );
    command[
0 ] : =   5 ;
    
if  bIsValid then
      command[
1 ] : =   2
    
else
      command[
1 ] : =   1 ;
    
if  bIsValid then
      command[
2 ] : =   2
    
else
      command[
2 ] : =   0 ;
 
    
// 发送登陆指令
     if  bIsValid then
      re :
=  WinSock.send(sock ^ , command,  4 0 )
    
else
      re :
=  WinSock.send(sock ^ , command,  3 0 );
    
if  re  =  SOCKET_ERROR then
    begin
      Result :
=   1 ;
      Exit;
    end;
 
    
// 接收返回的消息
    fillchar(command,  10 0 );                          // 接收前用0再次填充
    re : =  WinSock.recv(sock ^ , command,  2 0 );
    
if  re  <>   2  then
    begin
      Result :
=   1 ;
      Exit;
    end;
    
if  command[ 1 ] = $FF then
    begin
      Result :
=   1 ;
      Exit;
    end;
 
    
if  (not bIsValid) and (command[ 1 ] = 0 ) then
    begin
      Exit;
    end;
 
    proxyUser :
=  FProxyParam.GetUsername;
    proxyPwd :
=  FProxyParam.GetPassword;
 
    
if  command[ 1 <>   0  then
    begin
      
if  command[ 1 <>   2  then
      begin
        Result :
=   1 ;
        Exit;
      end;
      
if  bIsValid then
      begin
        ulen :
=  Length(proxyUser);
        plen :
=  Length(proxyPwd);
        len :
=   3   +  ulen  +  plen;
        fillchar(buffer, 
1024 0 );
 
        buffer[
0 ] : =   5 ;
        buffer[
1 ] : =  ulen;
        StrPCopy(@buffer[
2 ], proxyuser);
        buffer[
2   +  ulen] : =  plen;
        StrPCopy(@buffer[
2   +  ulen  +   1 ], proxyPwd);
 
        
// 发送验证信息
        re : =  send(sock ^ , buffer, len,  0 );
        
if  re  =  SOCKET_ERROR then
        begin
          Result :
=   1 ;
          Exit;
        end;
 
        
// 接收验证返回信息
        fillchar(command,  10 0 );
        re :
=  recv(sock ^ , command,  2 0 );
        
if  ((re <> 2 ) or ((command[ 0 ] <> 1 ) and (command[ 1 ] <> 0  ))) then
        begin
          Result :
=   1 ;
          Exit;
        end;
      end   
// if bisValid
       else
      begin
        Result :
=   1 ;
        Exit;
      end;                     
//
    end;  //  if command[1]<>0
  end;   // end first if
 
end;
上面的函数中有一个FproxyParam变量,它是代理参数的值对象,声明如下:
  TProxyParam 
=   class
  
private
    FUsername,        
// 代理验证用户名
    FPassword,         // 代理验证密码
    FServer: String;     // 代理服务器地址
    FPort: Integer;      // 代理服务器端口号
    FIsValid: Boolean;   // 是否验证     如果代理服务器是验证的,那么此值应该为true
 
    procedure Clear;
  
public
    constructor Create;
    procedure SetUsername(AUsername: String);
    procedure SetPassword(APassword: String);
    procedure SetServer(AServer: String);
    procedure SetPort(APort: Integer);
    procedure SetProxyValid(AValid: Boolean);
 
    function GetUsername: String;
    function GetPassword: String;
    function GetServer: String;
    function GetPort: Integer;
    function GetProxyValid: Boolean;
  end;
使用此函数:
假设已经创建了一个ClientSocket1(TclientSocket对象),它的IP和Port设置为代理服务器的IP和Port,那么就有下面的代码:
......
Var
Re:Integer;
Begin
Re:
= SetProxy(@(ClientSocket.Socket.SocketHandle)) ;   // 如果验证成功,那么就可以用返回的//socket进行相应的通讯
If Re = 0  then 
Begn
  ShowMessage(‘Sock5 is ok’);         
// 代理服务器就会有连接的数据流显示
End
Else begin
  ShowMessage(‘Sock5 is error’);
End;
End;
……
上面的例子如果设置代理成功,那么代理服务器就会有连接的数据流显示。表示与代理服务器有数据交换,如果没有成功则务数据流显示。
 
 
                                                               李孟岩
                                                                      [email protected]
                                                                                    2005-07-05实现Sock5代理

你可能感兴趣的:(实现Sock5代理)