Socks5代理客户端实现
转自:http://blog.csdn.net/lostpen/article/details/429089#t3
有很多公司不能直接和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;
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;
……
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;
……
上面的例子如果设置代理成功,那么代理服务器就会有连接的数据流显示。表示与代理服务器有数据交换,如果没有成功则务数据流显示。
版权声明:本文为zahuifan原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。