积极答复者
如何访问Socket5 代理?

问题
-
LRESULT CClientSocket::OnSocketNotifyProxyRead() { char* cRecvBuf=0; int iRecvBufPos=0; WORD wRecvSize=0; WORD iRecvBufferLen=0; int nServerIP = inet_addr(m_szRemoteIP); if (m_nProxyOpState==1) { if (!cRecvBuf) cRecvBuf=new char[2]; //读取数据 int iRetCode=recv(m_hSocket,(char *)cRecvBuf,sizeof(cRecvBuf),0); if (iRetCode==SOCKET_ERROR) throw TEXT("读取代理服务器数据失败"); m_wRecvSize+=iRetCode; if(m_wRecvSize==2) { if(cRecvBuf[0]!= 5) throw TEXT("代理服务器数据异常"); if(cRecvBuf[1]) //需要验证 { if (cRecvBuf[1]!=2) throw TEXT("不支持的代理服务器验证方式"); //发送验证数据 LPCSTR lpszAsciiUser = m_ProxyInfo.strProxyName; LPCSTR lpszAsciiPass = m_ProxyInfo.strProxyPass; ASSERT(strlen(lpszAsciiUser)<=255); ASSERT(strlen(lpszAsciiPass)<=255); unsigned char *buffer = new unsigned char[3 + (lpszAsciiUser?strlen(lpszAsciiUser):0) + (lpszAsciiPass?strlen(lpszAsciiPass):0) + 1]; sprintf((char *)buffer, " %s %s", lpszAsciiUser?lpszAsciiUser:"", lpszAsciiPass?lpszAsciiPass:""); buffer[0]=1; buffer[1]=static_cast<unsigned char>(strlen(lpszAsciiUser)); buffer[2+strlen(lpszAsciiUser)]=static_cast<unsigned char>(strlen(lpszAsciiPass)); int len=3+strlen(lpszAsciiUser)+strlen(lpszAsciiPass); int res=send(m_hSocket,(LPSTR)&buffer,len,0); delete [] buffer; if (res==SOCKET_ERROR || res<len) { throw TEXT("代理服务器数据异常"); } m_nProxyOpState++; return 1; } //不需要验证 char *command=new char[10]; memset(command,0,10); command[0]=5; command[1]=1; //请求类型 1:CONNECT,2:END,3:UDP ASSOCIATE command[2]=0; command[3]=1; //地址类型 1:IPv4, 3:域名, 4:IPv6 int len=4; if (nServerIP) { memcpy(&command[len],&nServerIP,4); len+=4; } int nPort = htons(m_wRemotePort); memcpy(&command[len],&nPort,2); len+=2; int res=send(m_hSocket,command,len,0); delete [] command; if (res==SOCKET_ERROR || res<len) { throw TEXT("代理服务器数据异常"); } m_nProxyOpState+=2; return 1; } } else if(m_nProxyOpState==2) { if (!cRecvBuf) cRecvBuf=new char[2]; int iRetCode=recv(m_hSocket,cRecvBuf+iRecvBufPos, 2-iRecvBufPos,0); if (iRetCode==SOCKET_ERROR) { throw TEXT("代理服务器数据异常"); } iRecvBufPos+=iRetCode; if (iRecvBufPos==2) { if (cRecvBuf[1]!=0) { throw TEXT("代理服务器数据异常"); } LPCSTR lpszAsciiHost =m_ProxyInfo.strProxyServer; char *command=new char[10+strlen(lpszAsciiHost)+1]; memset(command,0,10+strlen(lpszAsciiHost)+1); command[0]=5; command[1]=1; command[2]=0; command[3]=1; int len=4; if (nServerIP) { memcpy(&command[len],&nServerIP,4); len+=4; } int nPort = htons(m_wRemotePort); memcpy(&command[len],&nPort,2); len+=2; int res=send(m_hSocket,command,len,0); delete [] command; if (res==SOCKET_ERROR || res<len) { throw TEXT("代理服务器数据异常"); } m_nProxyOpState++; return 1; }; } else if(m_nProxyOpState==3) { if (!cRecvBuf) { cRecvBuf=new char[10]; iRecvBufferLen = 10; }; int iRetCode=recv(m_hSocket,cRecvBuf+iRecvBufPos, iRecvBufferLen-iRecvBufPos,0); if (iRetCode==SOCKET_ERROR) { throw TEXT("代理服务器数据异常"); }; iRecvBufPos+=iRetCode; if (iRecvBufPos==iRecvBufferLen) { if (cRecvBuf[1]!=0 || cRecvBuf[0]!=5) { static TCHAR szBuffer[64]; _snprintf(szBuffer,sizeof(szBuffer),TEXT("代理服务器连接游戏服务器错误,错误代码 [ %d ]"),cRecvBuf[1]); throw szBuffer; }; SOCKADDR_IN SocketBindHostAddr; if (cRecvBuf[3]==1) { // 得到代理绑定地址 unsigned long ip; int port; ASSERT(cRecvBuf[3]==1); memcpy(&ip,&cRecvBuf[4],4); memcpy(&port,&cRecvBuf[8],2); static TCHAR szBuffer[64]; _snprintf(szBuffer,sizeof(szBuffer),TEXT("IP[%d],Port[%d]"),ip,port); memset(&SocketBindHostAddr,0,sizeof(SocketBindHostAddr)); SocketBindHostAddr.sin_family=AF_INET; SocketBindHostAddr.sin_port=port; SocketBindHostAddr.sin_addr.S_un.S_addr=ip; } else { char *tmp=new char[iRecvBufferLen+=cRecvBuf[4]+2]; memcpy(tmp,cRecvBuf,5); delete [] cRecvBuf; cRecvBuf=tmp; iRecvBufferLen+=cRecvBuf[4]+2; }; m_nProxyOpState++; // 得到本机绑定地址 int len = sizeof(SocketBindHostAddr); getsockname(m_hSocket,(SOCKADDR*)&SocketBindHostAddr,&len); m_pIClientSocketSink->OnSocketConnect(0,TEXT(""),this); return 1; }; } else { return 0; }; }
大家帮忙看看 这是一个和Socket5代理服务器通信得函数。当m_nProxyOpState==3时,代理服务器应该返回 连接信息 ,这部分我就处理不明白了!希望高人指点。
答案
-
朋友,您好
通过Socket5代理服务器访问网络的问题,我将以代码的形式说明并讲解你的问题。
Socket5版本的协议说明参考 RFC1928,RFC1929
下面简单地罗列程序实现,不涉及详细的协议规范。首先对于TCP连接,然后再讨论UDP传输。至于通过socket5的多播通讯,有兴趣可以参考MSDN 技术文章中D. Chouinard的文章。
1、TCP:
// 建立流套接字
SOCKET m_socTCP=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 连接到代理服务器
int nRet = connect(m_socTCP,(SOCKADDR*)&m_saiProxy,sizeof(m_saiProxy));
// Step 1: 连接代理服务器成功后,马上开始和代理协商,协商报文如下,询问服务器,版本5,是需要验证(0x02)还是不需要验证(0x00)
+------+-------------------+------------+
|VER | Number of METHODS | METHODS |
+------+-------------------+------------+
| 0x05 | 0x02 (有两个方法) | 0x00 | 0x02|
+------+-------------------+------------+
const char reqNego[4]={(char)0x05,(char)0x02,(char)0x00,(char)0x02};
nRet = send(m_socTCP,reqNego,4,0);
// Setp 2: 代理服务器将返回两个字节的协商结果,接收协商结果
fd_set fdread;FD_ZERO(&fdread);
FD_SET(m_socTCP,&fdread);
// Last param set to NULL for blocking operation. (struct timeval*)
if((nRet=select(0,&fdread,NULL,NULL,NULL))==SOCKET_ERROR){return NC_E_PROXY_SELECT_READ|WSAGetLastError();}
char resNego[2]={'\0'};
int nRcvd=0,nCount=0;
while(1)
{
if(FD_ISSET(m_socTCP,&fdread))
{
//接收sock[0]发送来的数据
do{
nRet = recv(m_socTCP, (char*)resNego+nRcvd, 2-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=2)&&(++nCount<1000));
if(nRcvd==2) break;
}
if(nCount++>=2000){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
}
if(resNego[0]!=0x05 || (resNego[1]!=0x00 && resNego[1]!=0x02)){return NC_E_PROXY_PROTOCOL_VERSION|WSAGetLastError();};
// Step 3: 根据协商结果判断是否需要验证用户,如果是0x02,则需要提供验证,验证部分参考RFC1929
if(resNego[1]==0x02)
{
// 需要密码验证
char reqAuth[513]={'\0'};
BYTE byLenUser = (BYTE)strlen(m_szProxyUserName);
BYTE byLenPswd = (BYTE)strlen(m_szProxyPassword);
reqAuth[0]=0x01;
reqAuth[1]=byLenUser;
sprintf(&reqAuth[2],"%s",m_szProxyUserName);
reqAuth[2+byLenUser]=byLenPswd;
sprintf(&reqAuth[3+byLenUser],"%s",m_szProxyPassword);
//Send authentication info
int len = (int)byLenUser + (int)byLenPswd + 3;
nRet=send(m_socTCP,(const char*)reqAuth,len,0);
if (nRet==SOCKET_ERROR){return NC_E_PROXY_SEND|WSAGetLastError();}
//Now : Response to the auth request
char resAuth[2]={'\0'};
int nRcvd=0,nCount=0;
do{
nRet = recv(m_socTCP,resAuth+nRcvd,2-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=2)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
if (resAuth[1]!=0) return NC_E_PROXY_AUTHORIZE;
// 密码验证通过了
}
// Step 4: 协商完成,开始发送连接远程服务器请求,请求报文格式如下:
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | 0x00 | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
// CMD==0x01 表示连接, ATYP==0x01表示采用IPV4格式地址,DST.ADDR是远程服务器地址,DST.PORT是远程服务器端口
// 如果需要接受外来连接,则需要在连接完成之后,发送CMD==0x02绑定请求,代理将为此请求绑定一个套接字接受外部连接
char reqSubNego[10]={(char)0x05,(char)0x01,(char)0x00,(char)0x01,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00};
*(unsigned long*)&reqSubNego[4] =m_saiServerTCP.sin_addr.S_un.S_addr;
*(unsigned short*)&reqSubNego[8]=m_saiServerTCP.sin_port;
nRet=send(m_socTCP,(const char*)reqSubNego,10,0);
if (nRet==SOCKET_ERROR){return NC_E_PROXY_SEND|WSAGetLastError();}
// Step 5: 接收对请求的响应,响应包格式如下
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | 0x00 | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
// VER 必须是0x05, REP==0x00表示成功,ATYP==0x01表示地址是IPV4地址,BND.ADDR 是代理为连接远程服务器绑定的地址,BND.PORT是这个套接字的端口
char resSubNego1[5]={'\0'};
if(FD_ISSET(m_socTCP,&fdread))
{
int nRcvd=0,nCount=0;
do{
nRet = recv(m_socTCP,resSubNego1+nRcvd,5-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=5)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
if(resSubNego1[0]!=0x05||resSubNego1[1]!=0x00){return NC_E_PROXY_PROTOCOL_VERSION_SUB|WSAGetLastError();};
switch(resSubNego1[3])
{
case 0x01:
{
// IP V4
char resSubNego2[6]={resSubNego1[4],0};
int nRet=-1;
if(FD_ISSET(m_socTCP,&fdread))
{
int nRcvd=0,nCount=0;
do{
int nRet = recv(m_socTCP,&resSubNego2[1]+nRcvd,5-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=5)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
}
// 得到代理绑定地址
unsigned long ulBINDAddr = *(unsigned long*)&resSubNego2; // SOCKS BIND ADDR
unsigned short usBINDPort = *(unsigned short*)&resSubNego2[4]; // SOCKS BIND PORT
m_saiProxyBindTCP.sin_addr.S_un.S_addr=ulBINDAddr;
m_saiProxyBindTCP.sin_port=usBINDPort;
// 得到本机绑定地址
int len = sizeof(m_saiHostTCP);
getsockname(m_socTCP,(SOCKADDR*)&m_saiHostTCP,&len);
}
break;
case 0x03:
{
// Domain name
int nLen = resSubNego1[4]+2;
char* presSubNego2 = new char[nLen];
if(FD_ISSET(m_socTCP,&fdread))
{
int nRet=0,nRcvd=0,nCount=0;
do{
nRet = recv(m_socTCP,presSubNego2+nRcvd,nLen-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=nLen)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
}
unsigned short usBINDPort = *(unsigned short*)(presSubNego2+nLen-2); // BIND PORT;
// 此时得到的是远程主机的Domain Name
delete[] presSubNego2; presSubNego2=NULL;
}
break;
case 0x04:
{
// IPV6
AfxMessageBox("该版本不支持IPV6";
}
break;
default:
break;
}
// 至此,连接已经建立。在此套接字上可进行数据的收发。
}
2、UDP
SOCKS V5提供了对UDP的支持,它通过在代理服务器和内网主机之间建立一个UDP中继的TCP连接,来辅助进行UDP数据包的收发。此连接有一个有效期,在此有效生命周期内,必须往代理发送UDP数据报确认连接有效,来维持此连接的有效性,否则,代理服务器超时,将会自动释放此连接的资源。
下面简单地罗列程序实现,不涉及详细的协议规范。
// 客户端为UDP中继建立一个相关的流套接字
SOCKET m_socClientTCP_UdpAssociate = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 连接代理服务器
int nRet=connect(m_socClientTCP_UdpAssociate,(SOCKADDR*)&saiProxy,sizeof(saiProxy));
// 连接成功,开始和代理服务器协商,首先发送版本标志,方法选择报文
const char reqNego[4]={(char)0x05,(char)0x02,(char)0x00,(char)0x02};
nRet = send(m_socClientTCP_UdpAssociate,reqNego,4,0);
if( nRet==SOCKET_ERROR
{
DWORD dwError = WSAGetLastError();
if (dwError!=WSAEWOULDBLOCK) return NCM_E_WM_CREATE_PROXYREQUESTFAILED;
}
// 接收协商的响应
fd_set fdread; FD_ZERO(&fdread);
FD_SET(m_socClientTCP_UdpAssociate,&fdread);
if((nRet=select(0,&fdread,NULL,NULL,NULL))==SOCKET_ERROR) return NCM_E_WM_CREATE_PROXYCONNECTFAILED;
char resNego[2]={0};
int nRcvd=0,nCount=0;
if(FD_ISSET(m_socClientTCP_UdpAssociate,&fdread))
{
nRcvd = recv(m_socClientTCP_UdpAssociate, (char*)resNego+nRcvd, 2,0);
if(nRcvd==SOCKET_ERROR) return NCM_E_WM_CREATE_PROXYCONNECTFAILED;
}
if(resNego[0]!=0x05 || (resNego[1]!=0x00 && resNego[1]!=0x02)) return NCM_E_WM_CREATE_PROXYCONNECTFAILED;
// 看是否需要密码验证
if(resNego[1]==0x02)
{
// 需要密码验证
char reqAuth[513]; memset(reqAuth,0,513);
BYTE byLenUser = (BYTE)strlen(m_szProxyUserName);
BYTE byLenPswd = (BYTE)strlen(m_szProxyPassword);
reqAuth[0]=0x01;
reqAuth[1]=byLenUser;
sprintf(&reqAuth[2],"%s",m_szProxyUserName);
reqAuth[2+byLenUser]=byLenPswd;
sprintf(&reqAuth[3+byLenUser],"%s",m_szProxyPassword);
//Send authentication info
int len = (int)byLenUser + (int)byLenPswd + 3;
int ret=send(m_socClientTCP_UdpAssociate,(const char*)reqAuth,len,0);
if (ret==SOCKET_ERROR) if (GetLastError()!=WSAEWOULDBLOCK) return NCM_E_WM_CREATE_PROXYREQUESTFAILED;
//Now : Response to the auth request
char resAuth[2]={'\0'};
int nRcvd=0,nCount=0;
do{
ret = recv(m_socClientTCP_UdpAssociate,resAuth+nRcvd,2-nRcvd,0);
if(ret==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=2)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE}
if (resAuth[1]!=0) return NEM_E_WM_CREATE_PROXYAUTHFAILED;
// 密码验证通过了
}
// 开始发送向目标服务器的连接请求,其中DST.ADDR是目标服务器的地址,DST.PORT是目标服务器的UDP端口
char reqSubNego[10]={(char)0x05,(char)0x03,(char)0x00,(char)0x01,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00};
*(unsigned long*)&reqSubNego[4] =saiServerUDP.sin_addr.S_un.S_addr; // cmd: DEST.addr
*(unsigned short*)&reqSubNego[8]=saiServerUDP.sin_port; // cmd: DEST.port in network octet order
nRet=send(m_socClientTCP_UdpAssociate,(const char*)reqSubNego,10,0);
if (nRet==SOCKET_ERROR) return NEM_E_WM_CREATE_PROXYREQFAILED;
// 接收响应信息
int nRecvCount = 0;
int nRecvBufferLen = 10;
char szRecvBuf[10];
nRet = 0;
if(FD_ISSET(m_socClientTCP_UdpAssociate,&fdread))
{
int nRcvd=0,nCount=0;
do{
nRet = recv(m_socClientTCP_UdpAssociate,(char*)szRecvBuf+nRcvd,10-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=10)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE;}
if (szRecvBuf[0]!=0x05||szRecvBuf[1]!=0x00){return NC_E_PROXY_PROTOCOL_VERSION_SUB;}
}
else
{
return NCM_E_WM_CREATE_PROXYREQUESTFAILED;
}
// 代理服务器绑定udp地址BND.ADR,一般代理服务器都是多宿主机器,因此这个绑定地址是局域网地址,代理服务器绑定udp端口BND.PORT
// m_ulProxyUDPAddr 代理绑定地址
// m_usUDPAssociatePort 代理绑定端口
memmove(&m_ulProxyUDPAddr,&szRecvBuf[4],4);
memmove(&m_usUDPAssociatePort,&szRecvBuf[8],2);
m_bUDPAssociated = TRUE;
// 至此,得到了代理绑定的地址和端口,客户端就可以通过它来收发UDP数据报了
// 客户端收发实例
// 首先创建数据报套接字,绑定套接字,得到本地数据报套接字地址,端口
SOCKET m_socClientUDP=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
SOCKADDR_IN saiLocal;
memset(&saiLocal,0,sizeof(saiLocal));
saiLocal.sin_family = AF_INET;
saiLocal.sin_port = 0;
saiLocal.sin_addr.S_un.S_addr = INADDR_ANY;
// 绑定本地udp套接字地址,地址+端口由系统决定
if (bind(m_socClientUDP, (SOCKADDR *)&saiLocal, sizeof(saiLocal)== SOCKET_ERROR)
getsockname(m_socClientUDP, (sockaddr*)&saiLocal, &len);
// m_ulHostAddr 本机绑定地址
// m_usHostPortUdp 本机绑定端口
m_ulHostAddr = saiLocal.sin_addr.S_un.S_addr;
m_usHostPortUdp = saiLocal.sin_port;
// 按照格式,发送数据
SOCKADDR_IN saiProxy;
memset(&saiProxy,0,sizeof(saiProxy));
saiProxy.sin_family = AF_INET;
saiProxy.sin_addr.S_un.S_addr = m_ulProxyUDPAddr; // 代理绑定的udp地址
saiProxy.sin_port = m_usUDPAssociatePort; // 代理绑定的udp端口
// 每个UDP包必须携带如下所述的头:
+----+------+------+----------+----------+----------+
|RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
+----+------+------+----------+----------+----------+
| 2 | 1 | 1 | Variable | 2 | Variable |
+----+------+------+----------+----------+----------+
// 其中 ATYP==0x01 表示采用 IPV4 地址,那么头长度就是10,DST.ADDR并不是服务器地址,而是本机绑定地址,DST.PORT是本机绑定端口
char buf[10+2]={'0x00','0x00','0x00','0x01','0xff'};
memmove(&buf[4],&m_ulHostAddr,4);
*(unsigned short*)&buf[8]=m_usHostPortUDP;
int nRet = sendto(m_socClientUDP,buf,12,0,(SOCKADDR*)&saiProxy,sizeof(saiProxy));
// 接收数据
int nLen = sizeof(saiProxy);
char buf=new char[MAX_UDPLEN];
nRet = recvfrom(m_socClientUDP,buf,MAX_UDPLEN,0,(SOCKADDR *)&saiProxy,&nLen);
if(nRet==SOCKET_ERROR) return SOCKET_ERROR;
BYTE flag=0xff;
if(memcmp(&buf[0],&flag,1)==0) return SOCKET_ERROR;
BYTE byProxyHead[20];
byProxyHead[0]=byProxyHead[1]=byProxyHead[2]=0;
byProxyHead[3]=1;
*(unsigned long*)&byProxyHead[4] = saiServerUDP.sin_addr.S_un.S_addr;
*(unsigned short*)&byProxyHead[8] = saiServerUDP.sin_port;
byProxyHead[10]=byProxyHead[11]=byProxyHead[12]=0;
byProxyHead[13]=1;
*(unsigned long*)&byProxyHead[14] = m_ulHostAddr;
*(unsigned short*)&byProxyHead[18] = m_usHostPortUdp;
int i=0;
BOOL bIsForMe=FALSE;
if(memcmp(&byProxyHead[0],&buf[i],4)==0)
{
unsigned long ulRetAddr = *(unsigned long*)&buf[i+4];
if(ulRetAddr==m_ulHostAddr)
{
unsigned short usRetPort = ntohs((unsigned short)(*(unsigned short*)&buf[i+8]));
if(usRetPort==m_usHostPortUdp)
{
bIsForMe=TRUE;
}
}
i+=10;
}
// 客户端收发结束
// 服务器端发送实例
// m_ulProxyUDPAddr 代理绑定地址
// m_usUDPAssociatePort 代理绑定端口
// m_ulHostAddr 本机绑定地址
// m_usHostPortUdp 本机绑定端口
// 客户端必须把上面的m_ulProxyUDPAddr,m_usUDPAssociatePort,m_ulHostAddr,m_usHostPortUdp告知服务器,服务器通过这两个套接字进行数据的收发
// 服务器创建数据报套接字
SOCKET m_socUDP=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
// 代理的udp中继地址
SOCKADDR_IN saiClient;
saiClient.sin_family = AF_INET;
saiClient.sin_addr.S_un.S_addr = m_ulProxyUDPAddr;
saiClient.sin_port = m_usUDPAssociatePort;
// 如果远程目标在代理服务器后面,透过代理,指定远程目标的socks信息,参照RFC1928
char buffer[10]={'\0'};
buffer[0]=buffer[1]=buffer[2]=0;
buffer[3]=1; // No Fragment, It's a dependent udp, no need for socks server to arrange udp fragments
// 目标机器的地址端口,透过代理后发向这里
*(int*)&buffer[4]=m_ulHostAddr;
*(unsigned short*)&buffer[8]=m_usHostPortUdp;
BYTE buf[DATA_BUFSIZE]={'\0'};
memmove(&buf[10],&szSendbuf[0],dwLength);
memmove(&buf[0],&buffer[0],10);
int nSent = sendto(m_socUDP, (const char*)&buf[0], dwLength+10, 0, (SOCKADDR*)&saiClient,sizeof(saiClient));
// 服务器端发送结束
Smile service,common progress!- 已标记为答案 Tim Li 2009年10月16日 10:00
全部回复
-
朋友,您好
通过Socket5代理服务器访问网络的问题,我将以代码的形式说明并讲解你的问题。
Socket5版本的协议说明参考 RFC1928,RFC1929
下面简单地罗列程序实现,不涉及详细的协议规范。首先对于TCP连接,然后再讨论UDP传输。至于通过socket5的多播通讯,有兴趣可以参考MSDN 技术文章中D. Chouinard的文章。
1、TCP:
// 建立流套接字
SOCKET m_socTCP=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 连接到代理服务器
int nRet = connect(m_socTCP,(SOCKADDR*)&m_saiProxy,sizeof(m_saiProxy));
// Step 1: 连接代理服务器成功后,马上开始和代理协商,协商报文如下,询问服务器,版本5,是需要验证(0x02)还是不需要验证(0x00)
+------+-------------------+------------+
|VER | Number of METHODS | METHODS |
+------+-------------------+------------+
| 0x05 | 0x02 (有两个方法) | 0x00 | 0x02|
+------+-------------------+------------+
const char reqNego[4]={(char)0x05,(char)0x02,(char)0x00,(char)0x02};
nRet = send(m_socTCP,reqNego,4,0);
// Setp 2: 代理服务器将返回两个字节的协商结果,接收协商结果
fd_set fdread;FD_ZERO(&fdread);
FD_SET(m_socTCP,&fdread);
// Last param set to NULL for blocking operation. (struct timeval*)
if((nRet=select(0,&fdread,NULL,NULL,NULL))==SOCKET_ERROR){return NC_E_PROXY_SELECT_READ|WSAGetLastError();}
char resNego[2]={'\0'};
int nRcvd=0,nCount=0;
while(1)
{
if(FD_ISSET(m_socTCP,&fdread))
{
//接收sock[0]发送来的数据
do{
nRet = recv(m_socTCP, (char*)resNego+nRcvd, 2-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=2)&&(++nCount<1000));
if(nRcvd==2) break;
}
if(nCount++>=2000){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
}
if(resNego[0]!=0x05 || (resNego[1]!=0x00 && resNego[1]!=0x02)){return NC_E_PROXY_PROTOCOL_VERSION|WSAGetLastError();};
// Step 3: 根据协商结果判断是否需要验证用户,如果是0x02,则需要提供验证,验证部分参考RFC1929
if(resNego[1]==0x02)
{
// 需要密码验证
char reqAuth[513]={'\0'};
BYTE byLenUser = (BYTE)strlen(m_szProxyUserName);
BYTE byLenPswd = (BYTE)strlen(m_szProxyPassword);
reqAuth[0]=0x01;
reqAuth[1]=byLenUser;
sprintf(&reqAuth[2],"%s",m_szProxyUserName);
reqAuth[2+byLenUser]=byLenPswd;
sprintf(&reqAuth[3+byLenUser],"%s",m_szProxyPassword);
//Send authentication info
int len = (int)byLenUser + (int)byLenPswd + 3;
nRet=send(m_socTCP,(const char*)reqAuth,len,0);
if (nRet==SOCKET_ERROR){return NC_E_PROXY_SEND|WSAGetLastError();}
//Now : Response to the auth request
char resAuth[2]={'\0'};
int nRcvd=0,nCount=0;
do{
nRet = recv(m_socTCP,resAuth+nRcvd,2-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=2)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
if (resAuth[1]!=0) return NC_E_PROXY_AUTHORIZE;
// 密码验证通过了
}
// Step 4: 协商完成,开始发送连接远程服务器请求,请求报文格式如下:
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | 0x00 | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
// CMD==0x01 表示连接, ATYP==0x01表示采用IPV4格式地址,DST.ADDR是远程服务器地址,DST.PORT是远程服务器端口
// 如果需要接受外来连接,则需要在连接完成之后,发送CMD==0x02绑定请求,代理将为此请求绑定一个套接字接受外部连接
char reqSubNego[10]={(char)0x05,(char)0x01,(char)0x00,(char)0x01,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00};
*(unsigned long*)&reqSubNego[4] =m_saiServerTCP.sin_addr.S_un.S_addr;
*(unsigned short*)&reqSubNego[8]=m_saiServerTCP.sin_port;
nRet=send(m_socTCP,(const char*)reqSubNego,10,0);
if (nRet==SOCKET_ERROR){return NC_E_PROXY_SEND|WSAGetLastError();}
// Step 5: 接收对请求的响应,响应包格式如下
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | 0x00 | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
// VER 必须是0x05, REP==0x00表示成功,ATYP==0x01表示地址是IPV4地址,BND.ADDR 是代理为连接远程服务器绑定的地址,BND.PORT是这个套接字的端口
char resSubNego1[5]={'\0'};
if(FD_ISSET(m_socTCP,&fdread))
{
int nRcvd=0,nCount=0;
do{
nRet = recv(m_socTCP,resSubNego1+nRcvd,5-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=5)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
if(resSubNego1[0]!=0x05||resSubNego1[1]!=0x00){return NC_E_PROXY_PROTOCOL_VERSION_SUB|WSAGetLastError();};
switch(resSubNego1[3])
{
case 0x01:
{
// IP V4
char resSubNego2[6]={resSubNego1[4],0};
int nRet=-1;
if(FD_ISSET(m_socTCP,&fdread))
{
int nRcvd=0,nCount=0;
do{
int nRet = recv(m_socTCP,&resSubNego2[1]+nRcvd,5-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=5)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
}
// 得到代理绑定地址
unsigned long ulBINDAddr = *(unsigned long*)&resSubNego2; // SOCKS BIND ADDR
unsigned short usBINDPort = *(unsigned short*)&resSubNego2[4]; // SOCKS BIND PORT
m_saiProxyBindTCP.sin_addr.S_un.S_addr=ulBINDAddr;
m_saiProxyBindTCP.sin_port=usBINDPort;
// 得到本机绑定地址
int len = sizeof(m_saiHostTCP);
getsockname(m_socTCP,(SOCKADDR*)&m_saiHostTCP,&len);
}
break;
case 0x03:
{
// Domain name
int nLen = resSubNego1[4]+2;
char* presSubNego2 = new char[nLen];
if(FD_ISSET(m_socTCP,&fdread))
{
int nRet=0,nRcvd=0,nCount=0;
do{
nRet = recv(m_socTCP,presSubNego2+nRcvd,nLen-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=nLen)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
}
unsigned short usBINDPort = *(unsigned short*)(presSubNego2+nLen-2); // BIND PORT;
// 此时得到的是远程主机的Domain Name
delete[] presSubNego2; presSubNego2=NULL;
}
break;
case 0x04:
{
// IPV6
AfxMessageBox("该版本不支持IPV6";
}
break;
default:
break;
}
// 至此,连接已经建立。在此套接字上可进行数据的收发。
}
2、UDP
SOCKS V5提供了对UDP的支持,它通过在代理服务器和内网主机之间建立一个UDP中继的TCP连接,来辅助进行UDP数据包的收发。此连接有一个有效期,在此有效生命周期内,必须往代理发送UDP数据报确认连接有效,来维持此连接的有效性,否则,代理服务器超时,将会自动释放此连接的资源。
下面简单地罗列程序实现,不涉及详细的协议规范。
// 客户端为UDP中继建立一个相关的流套接字
SOCKET m_socClientTCP_UdpAssociate = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 连接代理服务器
int nRet=connect(m_socClientTCP_UdpAssociate,(SOCKADDR*)&saiProxy,sizeof(saiProxy));
// 连接成功,开始和代理服务器协商,首先发送版本标志,方法选择报文
const char reqNego[4]={(char)0x05,(char)0x02,(char)0x00,(char)0x02};
nRet = send(m_socClientTCP_UdpAssociate,reqNego,4,0);
if( nRet==SOCKET_ERROR
{
DWORD dwError = WSAGetLastError();
if (dwError!=WSAEWOULDBLOCK) return NCM_E_WM_CREATE_PROXYREQUESTFAILED;
}
// 接收协商的响应
fd_set fdread; FD_ZERO(&fdread);
FD_SET(m_socClientTCP_UdpAssociate,&fdread);
if((nRet=select(0,&fdread,NULL,NULL,NULL))==SOCKET_ERROR) return NCM_E_WM_CREATE_PROXYCONNECTFAILED;
char resNego[2]={0};
int nRcvd=0,nCount=0;
if(FD_ISSET(m_socClientTCP_UdpAssociate,&fdread))
{
nRcvd = recv(m_socClientTCP_UdpAssociate, (char*)resNego+nRcvd, 2,0);
if(nRcvd==SOCKET_ERROR) return NCM_E_WM_CREATE_PROXYCONNECTFAILED;
}
if(resNego[0]!=0x05 || (resNego[1]!=0x00 && resNego[1]!=0x02)) return NCM_E_WM_CREATE_PROXYCONNECTFAILED;
// 看是否需要密码验证
if(resNego[1]==0x02)
{
// 需要密码验证
char reqAuth[513]; memset(reqAuth,0,513);
BYTE byLenUser = (BYTE)strlen(m_szProxyUserName);
BYTE byLenPswd = (BYTE)strlen(m_szProxyPassword);
reqAuth[0]=0x01;
reqAuth[1]=byLenUser;
sprintf(&reqAuth[2],"%s",m_szProxyUserName);
reqAuth[2+byLenUser]=byLenPswd;
sprintf(&reqAuth[3+byLenUser],"%s",m_szProxyPassword);
//Send authentication info
int len = (int)byLenUser + (int)byLenPswd + 3;
int ret=send(m_socClientTCP_UdpAssociate,(const char*)reqAuth,len,0);
if (ret==SOCKET_ERROR) if (GetLastError()!=WSAEWOULDBLOCK) return NCM_E_WM_CREATE_PROXYREQUESTFAILED;
//Now : Response to the auth request
char resAuth[2]={'\0'};
int nRcvd=0,nCount=0;
do{
ret = recv(m_socClientTCP_UdpAssociate,resAuth+nRcvd,2-nRcvd,0);
if(ret==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=2)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE}
if (resAuth[1]!=0) return NEM_E_WM_CREATE_PROXYAUTHFAILED;
// 密码验证通过了
}
// 开始发送向目标服务器的连接请求,其中DST.ADDR是目标服务器的地址,DST.PORT是目标服务器的UDP端口
char reqSubNego[10]={(char)0x05,(char)0x03,(char)0x00,(char)0x01,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00};
*(unsigned long*)&reqSubNego[4] =saiServerUDP.sin_addr.S_un.S_addr; // cmd: DEST.addr
*(unsigned short*)&reqSubNego[8]=saiServerUDP.sin_port; // cmd: DEST.port in network octet order
nRet=send(m_socClientTCP_UdpAssociate,(const char*)reqSubNego,10,0);
if (nRet==SOCKET_ERROR) return NEM_E_WM_CREATE_PROXYREQFAILED;
// 接收响应信息
int nRecvCount = 0;
int nRecvBufferLen = 10;
char szRecvBuf[10];
nRet = 0;
if(FD_ISSET(m_socClientTCP_UdpAssociate,&fdread))
{
int nRcvd=0,nCount=0;
do{
nRet = recv(m_socClientTCP_UdpAssociate,(char*)szRecvBuf+nRcvd,10-nRcvd,0);
if(nRet==SOCKET_ERROR){return NC_E_PROXY_RECEIVE|WSAGetLastError();}
nRcvd += nRet;
}
while((nRcvd!=10)&&(++nCount<1000));
if(nCount>=1000){return NC_E_PROXY_RECEIVE;}
if (szRecvBuf[0]!=0x05||szRecvBuf[1]!=0x00){return NC_E_PROXY_PROTOCOL_VERSION_SUB;}
}
else
{
return NCM_E_WM_CREATE_PROXYREQUESTFAILED;
}
// 代理服务器绑定udp地址BND.ADR,一般代理服务器都是多宿主机器,因此这个绑定地址是局域网地址,代理服务器绑定udp端口BND.PORT
// m_ulProxyUDPAddr 代理绑定地址
// m_usUDPAssociatePort 代理绑定端口
memmove(&m_ulProxyUDPAddr,&szRecvBuf[4],4);
memmove(&m_usUDPAssociatePort,&szRecvBuf[8],2);
m_bUDPAssociated = TRUE;
// 至此,得到了代理绑定的地址和端口,客户端就可以通过它来收发UDP数据报了
// 客户端收发实例
// 首先创建数据报套接字,绑定套接字,得到本地数据报套接字地址,端口
SOCKET m_socClientUDP=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
SOCKADDR_IN saiLocal;
memset(&saiLocal,0,sizeof(saiLocal));
saiLocal.sin_family = AF_INET;
saiLocal.sin_port = 0;
saiLocal.sin_addr.S_un.S_addr = INADDR_ANY;
// 绑定本地udp套接字地址,地址+端口由系统决定
if (bind(m_socClientUDP, (SOCKADDR *)&saiLocal, sizeof(saiLocal)== SOCKET_ERROR)
getsockname(m_socClientUDP, (sockaddr*)&saiLocal, &len);
// m_ulHostAddr 本机绑定地址
// m_usHostPortUdp 本机绑定端口
m_ulHostAddr = saiLocal.sin_addr.S_un.S_addr;
m_usHostPortUdp = saiLocal.sin_port;
// 按照格式,发送数据
SOCKADDR_IN saiProxy;
memset(&saiProxy,0,sizeof(saiProxy));
saiProxy.sin_family = AF_INET;
saiProxy.sin_addr.S_un.S_addr = m_ulProxyUDPAddr; // 代理绑定的udp地址
saiProxy.sin_port = m_usUDPAssociatePort; // 代理绑定的udp端口
// 每个UDP包必须携带如下所述的头:
+----+------+------+----------+----------+----------+
|RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
+----+------+------+----------+----------+----------+
| 2 | 1 | 1 | Variable | 2 | Variable |
+----+------+------+----------+----------+----------+
// 其中 ATYP==0x01 表示采用 IPV4 地址,那么头长度就是10,DST.ADDR并不是服务器地址,而是本机绑定地址,DST.PORT是本机绑定端口
char buf[10+2]={'0x00','0x00','0x00','0x01','0xff'};
memmove(&buf[4],&m_ulHostAddr,4);
*(unsigned short*)&buf[8]=m_usHostPortUDP;
int nRet = sendto(m_socClientUDP,buf,12,0,(SOCKADDR*)&saiProxy,sizeof(saiProxy));
// 接收数据
int nLen = sizeof(saiProxy);
char buf=new char[MAX_UDPLEN];
nRet = recvfrom(m_socClientUDP,buf,MAX_UDPLEN,0,(SOCKADDR *)&saiProxy,&nLen);
if(nRet==SOCKET_ERROR) return SOCKET_ERROR;
BYTE flag=0xff;
if(memcmp(&buf[0],&flag,1)==0) return SOCKET_ERROR;
BYTE byProxyHead[20];
byProxyHead[0]=byProxyHead[1]=byProxyHead[2]=0;
byProxyHead[3]=1;
*(unsigned long*)&byProxyHead[4] = saiServerUDP.sin_addr.S_un.S_addr;
*(unsigned short*)&byProxyHead[8] = saiServerUDP.sin_port;
byProxyHead[10]=byProxyHead[11]=byProxyHead[12]=0;
byProxyHead[13]=1;
*(unsigned long*)&byProxyHead[14] = m_ulHostAddr;
*(unsigned short*)&byProxyHead[18] = m_usHostPortUdp;
int i=0;
BOOL bIsForMe=FALSE;
if(memcmp(&byProxyHead[0],&buf[i],4)==0)
{
unsigned long ulRetAddr = *(unsigned long*)&buf[i+4];
if(ulRetAddr==m_ulHostAddr)
{
unsigned short usRetPort = ntohs((unsigned short)(*(unsigned short*)&buf[i+8]));
if(usRetPort==m_usHostPortUdp)
{
bIsForMe=TRUE;
}
}
i+=10;
}
// 客户端收发结束
// 服务器端发送实例
// m_ulProxyUDPAddr 代理绑定地址
// m_usUDPAssociatePort 代理绑定端口
// m_ulHostAddr 本机绑定地址
// m_usHostPortUdp 本机绑定端口
// 客户端必须把上面的m_ulProxyUDPAddr,m_usUDPAssociatePort,m_ulHostAddr,m_usHostPortUdp告知服务器,服务器通过这两个套接字进行数据的收发
// 服务器创建数据报套接字
SOCKET m_socUDP=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
// 代理的udp中继地址
SOCKADDR_IN saiClient;
saiClient.sin_family = AF_INET;
saiClient.sin_addr.S_un.S_addr = m_ulProxyUDPAddr;
saiClient.sin_port = m_usUDPAssociatePort;
// 如果远程目标在代理服务器后面,透过代理,指定远程目标的socks信息,参照RFC1928
char buffer[10]={'\0'};
buffer[0]=buffer[1]=buffer[2]=0;
buffer[3]=1; // No Fragment, It's a dependent udp, no need for socks server to arrange udp fragments
// 目标机器的地址端口,透过代理后发向这里
*(int*)&buffer[4]=m_ulHostAddr;
*(unsigned short*)&buffer[8]=m_usHostPortUdp;
BYTE buf[DATA_BUFSIZE]={'\0'};
memmove(&buf[10],&szSendbuf[0],dwLength);
memmove(&buf[0],&buffer[0],10);
int nSent = sendto(m_socUDP, (const char*)&buf[0], dwLength+10, 0, (SOCKADDR*)&saiClient,sizeof(saiClient));
// 服务器端发送结束
Smile service,common progress!- 已标记为答案 Tim Li 2009年10月16日 10:00
-
首先感谢Mack Juesson
,你发的这个例子我看过了! 其中TCP方式连接代理时,在密码验证完成后,代理服务器会发回代理绑定地址。用的是如下方式获得的代理绑定地址
// 得到代理绑定地址 unsigned long ulBINDAddr = *(unsigned long*)&resSubNego2; // SOCKS BIND ADDR unsigned short usBINDPort = *(unsigned short*)&resSubNego2[4]; // SOCKS BIND PORT m_saiProxyBindTCP.sin_addr.S_un.S_addr=ulBINDAddr; m_saiProxyBindTCP.sin_port=usBINDPort; // 得到本机绑定地址 int len = sizeof(m_saiHostTCP); getsockname(m_socTCP,(SOCKADDR*)&m_saiHostTCP,&len);
我不明白getsockname 这个函数是干什么用的,起什么作用,还有执行完上述代码后是不是就可以直接使用m_socTCP访问网络了?不需要在考虑代理的因素了。
麻烦Mack Juesson 了。