使用 Winsock2 通过 SOCKS5 下载 HTTP 网站
Download HTTP Website through SOCKS5 using Winsock2
正如标题所说,我想使用Winsock2通过SOCKS5代理服务器下载一个网站源。该程序使用 MinGW 编译。
使用的文档来自RFC 1928。
为了向您介绍这个主题,为了在 SOCKS5 服务器和网络服务器之间建立连接,我必须向 socks 服务器发送两个请求。如果这两个都成功,那么我可以使用带有 send() 和 recv() 的套接字句柄通过我们刚刚创建的隧道与网络服务器交互。
根据 RFC,第一个数据包 看起来像:
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
我只想要一种方法,一种没有授权的方法。
我用这种方式构建了数据包:
char *initPacket1 = new char[3];
int initPacket1Length = sizeof(initPacket1) / sizeof(initPacket1[0]);
initPacket1[0] = 5; //SOCKS Version. [VER]
initPacket1[1] = 1; //No. of methods [NMETHODS]
initPacket1[2] = 0; //No auth required [X’00’]
if(send(hSocketSock, initPacket1, initPacket1Length, 0) == SOCKET_ERROR)
{
return -1;
}
响应应该是一个包含版本和方法的 2 字节数据包。
第一个问题 是这一步有时会失败。更具体地说,recv() 函数有效,但接收到的字节无效。但它只发生过几次。
接下来,我必须构建 第二个数据包 告诉代理服务器连接到目标网站。我必须构建的数据包具有以下形式:
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X’00’ | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
以及我构建数据包的方式:
char *initPacket2 = new char[10];
int initPacket2Length = sizeof(initPacket2) / sizeof(initPacket2[0]);
initPacket2[0] = 5; //SOCKS Version;
initPacket2[1] = 1; //1 = CONNECT, 2 = BIND, 3 = UDP ASSOCIATE;
initPacket2[2] = 0; //Reserved byte
initPacket2[3] = 1; //1 = IPv4, 3 = DOMAINNAME, 4 = IPv6
memcpy(initPacket2 + 4, &dest.sin_addr.S_un.S_addr, 4);
memcpy(initPacket2 + 8, &dest.sin_port, 2);
if(send(hSocketSock, initPacket2, initPacket2Length, 0) == SOCKET_ERROR)
{
return -1;
}
第二个大问题是响应字段每次都是“07”,意思是“不支持命令”。
我已经修改了第二个数据包的第二个字节,但没有成功。
很可能我在构建数据包时遗漏了一些东西,但我不知道是什么。
完整节目:
main.cpp
#include <winsock2.h>
#include <string>
#include <iostream>
#include "util.hpp"
using namespace std;
int main(void)
{
//SOCKS5 info
u_short sockPort = 45554;
std::string sockIp = "xx.xx.xx.xx";
//Destination info
u_short destPort = 80;
std::string destIPorURL = "checkip.dyndns.com";
//Check to see if winsock2 is available
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,0), &wsaData)==0)
{
if (LOBYTE(wsaData.wVersion) < 2)
{
cout << "WSA Version error!";
return -1;
}
}
else
{
cout << "WSA Startup Failed";
return -1;
}
///////////////////////////////////////////////////////////////////////////////////////////
//Init sockaddr for SOCKS5
cout << "Initialize sock_addr for socks4 connection...";
sockaddr_in sock;
sock.sin_family = AF_INET; // host byte order
sock.sin_port = htons( sockPort ); // short, network byte order
if(!utils::getHostIP(sock.sin_addr.S_un.S_addr, sockIp)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
cout << "done" << endl;
//Creating socket handler
cout << "Creating socket handler...";
SOCKET hSocketSock = INVALID_SOCKET;
if( (hSocketSock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET )
{
cout << "fail";
return -1;
}
cout << "done" << endl;
/////////////////////////////////////////////////////////////////////////////////////////////
//Init sockaddr for destination server
cout << "Initialize sock_addr for destination server...";
sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_port = htons( destPort );
if(!utils::getHostIP(dest.sin_addr.S_un.S_addr, destIPorURL)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
memset( &(dest.sin_zero), '[=14=]', 8 );
cout << "done" << endl;
////////////////////////////////////////////////////////////////////////////////////////////////
//Time to connect to SOCKS5 server
cout << "Connecting to sock server...";
if(connect(hSocketSock, reinterpret_cast<sockaddr *>(&sock), sizeof(sock)) != 0)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
//Documentation: https://tools.ietf.org/pdf/rfc1928.pdf
//We have to send first packet which tell to SOCKS5 to enter on
//sub-negociation mode so we can connect to actual destination server
// The client connects to the server, and sends a version
// identifier/method selection message:
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
//Allocate space for the first initialize packet and his replay
char *initPacket1 = new char[3];
int initPacket1Length = sizeof(initPacket1) / sizeof(initPacket1[0]);
char reply1[2];
memset( &reply1, '[=14=]' , strlen((const char *)reply1) );
initPacket1[0] = 5; //SOCKS Version. [VER]
initPacket1[1] = 1; //No. of methods [NMETHODS]
initPacket1[2] = 0; //No auth required [X’00’]
//Now we are sending the packet we just created
cout << "Sending first init packet...";
if(send(hSocketSock, initPacket1, initPacket1Length, 0) == SOCKET_ERROR)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
//And our expected replay format:
//
// The server selects from one of the methods given in METHODS, and
// sends a METHOD selection message:
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
//Receiving response from server
cout << "Waiting for response...";
if(recv(hSocketSock, reply1, 2, 0) == SOCKET_ERROR)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
//reply[0] = our version
//reply[1] = out method. [X’00’ NO AUTHENTICATION REQUIRED]
if( !(reply1[0] == 5 && reply1[1] == 0) )
{
cout << "Error: bad result on reply:" << (int)reply1[0] << " " << (int)reply1[1] << endl;
return -1;
}
//We can now delete forst packet
delete[] initPacket1;
//We have to build initialize packet. This will transmit to SOCKS5 server
//the web server we want to connect to.
//
// The SOCKS request is formed as follows:
//
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// Where:
// 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
//Building that packet
char *initPacket2 = new char[10];
int initPacket2Length = sizeof(initPacket2) / sizeof(initPacket2[0]);
char reply2[16];
int reply2Length = sizeof(reply2) / sizeof(reply2[0]);
initPacket2[0] = 5; //SOCKS Version;
initPacket2[1] = 1; //1 = CONNECT, 2 = BIND, 3 = UDP ASSOCIATE;
initPacket2[2] = 0; //Reserved byte
initPacket2[3] = 1; //1 = IPv4, 3 = DOMAINNAME, 4 = IPv6
memcpy(initPacket2 + 4, &dest.sin_addr.S_un.S_addr, 4);
memcpy(initPacket2 + 8, &dest.sin_port, 2);
//Send the second init packet to server. This will inform the SOCKS5 server
//what is our target.
cout << "Sending second init packet...";
if(send(hSocketSock, initPacket2, initPacket2Length, 0) == SOCKET_ERROR)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
//Reading the response
//Expected response format:
// The SOCKS request information is sent by the client as soon as it has
// established a connection to the SOCKS server, and completed the
// authentication negotiations. The server evaluates the request, and
// returns a reply formed as follows:
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// Where:
// 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
// ......................................
cout << "Waiting for response...";
if(recv(hSocketSock, reply2, reply2Length, 0) == SOCKET_ERROR)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
cout << "Returned packets: " << endl;
cout << "VER: " << (int)reply2[0] << endl;
cout << "REP: " << (int)reply2[1] << endl;
cout << "RSV: " << (int)reply2[2] << endl;
cout << "ATY: " << (int)reply2[3] << endl;
cout << endl;
}
返回的字节 REP 是 07,但我需要一个 0 才能继续。
我附上了一个包含有用功能的源文件,以备您测试。
util.hpp:
#include <winsock2.h>
#include <string>
#include <algorithm>
#include <vector>
namespace utils
{
std::string getHostFromUrl(std::string &url);
bool getHostIP(unsigned long &ipAddr, std::string urlOrHostnameOrIp);
namespace IPAddr
{
bool isValidIPv4(std::string &ip);
std::string reverseIpAddress(std::string ip);
std::string decimalToDottedIp(unsigned long ip);
unsigned long stripToDecimal(std::string &ip);
}
namespace strings
{
std::vector<std::string> split(std::string &s, char delim);
std::string removeSubstrs(std::string &source, std::string pattern);
}
};
util.cpp:
#include <stdexcept>
#include <iostream>
#include <sstream>
#include <stdio.h>
#include "util.hpp"
#define cout std::cout
#define endl std::endl
/////////////////////////////////////////////////////////////////////////////////////
// _ _ _ _ _
// | \ | | __ _ _ __ ___ ___ ___ _ __ __ _ ___ ___ _ _| |_(_) |___
// | \| |/ _` | '_ ` _ \ / _ \/ __| '_ \ / _` |/ __/ _ \ | | | | __| | / __|
// | |\ | (_| | | | | | | __/\__ \ |_) | (_| | (_| __/ | |_| | |_| | \__ \
// |_| \_|\__,_|_| |_| |_|\___||___/ .__/ \__,_|\___\___| \__,_|\__|_|_|___/
// |_|
/////////////////////////////////////////////////////////////////////////////////////
bool utils::getHostIP(unsigned long &ipAddr, std::string url)
{
HOSTENT *pHostent;
std::string hostname = getHostFromUrl(url);
if( utils::IPAddr::isValidIPv4(hostname) )
{
//IP Address must be reversed in order to be compatible with sockAddr.sin_addr.S_un.S_addr
//example: 192.168.1.2 => 2.1.168.192
hostname = utils::IPAddr::reverseIpAddress(hostname);
ipAddr = utils::IPAddr::stripToDecimal(hostname);
return true;
}
if (!(pHostent = gethostbyname(hostname.c_str())))
{
return false;
}
if (pHostent->h_addr_list && pHostent->h_addr_list[0])
{
ipAddr = *reinterpret_cast<unsigned long *>(pHostent->h_addr_list[0]);
return true;
}
return false;
}
std::string utils::getHostFromUrl(std::string &url)
{
std::string urlcopy = url;
urlcopy = utils::strings::removeSubstrs(urlcopy, "http://");
urlcopy = utils::strings::removeSubstrs(urlcopy, "www.");
urlcopy = utils::strings::removeSubstrs(urlcopy, "https://");
urlcopy = urlcopy.substr(0, urlcopy.find("/"));
return urlcopy;
}
// ___ ____ _ _ _
// | _ _|| _ \ / \ __| | __| | _ __ ___ ___ ___
// | | | |_) | / _ \ / _` | / _` || '__|/ _ \/ __|/ __|
// | | | __/ / ___ \| (_| || (_| || | | __/\__ \__ \
// |___||_| /_/ \_\__,_| \__,_||_| \___||___/|___/
bool utils::IPAddr::isValidIPv4(std::string &ipv4)
{
const std::string address = ipv4;
std::vector<std::string> arr;
int k = 0;
arr.push_back(std::string());
for (std::string::const_iterator i = address.begin(); i != address.end(); ++i)
{
if (*i == '.')
{
++k;
arr.push_back(std::string());
if (k == 4)
{
return false;
}
continue;
}
if (*i >= '0' && *i <= '9')
{
arr[k] += *i;
}
else
{
return false;
}
if (arr[k].size() > 3)
{
return false;
}
}
if (k != 3)
{
return false;
}
for (int i = 0; i != 4; ++i)
{
const char* nPtr = arr[i].c_str();
char* endPtr = 0;
const unsigned long a = ::strtoul(nPtr, &endPtr, 10);
if (nPtr == endPtr)
{
return false;
}
if (a > 255)
{
return false;
}
}
return true;
}
std::string utils::IPAddr::reverseIpAddress(std::string ip)
{
std::vector<std::string> octeti = utils::strings::split(ip, '.');
return (octeti[3] + "." + octeti[2] + "." + octeti[1] + "." + octeti[0]);
}
unsigned long utils::IPAddr::stripToDecimal(std::string &ip)
{
unsigned long a,b,c,d,base10IP;
sscanf(ip.c_str(), "%lu.%lu.%lu.%lu", &a, &b, &c, &d);
// Do calculations to convert IP to base 10
a *= 16777216;
b *= 65536;
c *= 256;
base10IP = a + b + c + d;
return base10IP;
}
std::string utils::IPAddr::decimalToDottedIp(unsigned long ipAddr)
{
unsigned short a, b, c, d;
std::ostringstream os ;
std::string ip = "";
a = (ipAddr & (0xff << 24)) >> 24;
b = (ipAddr & (0xff << 16)) >> 16;
c = (ipAddr & (0xff << 8)) >> 8;
d = ipAddr & 0xff;
os << d << "." << c << "." << b << "." << a;
ip = os.str();
return ip;
}
// ____ _ _
// / ___| | |_ _ __ (_) _ __ __ _ ___
// \___ \ | __|| '__|| || '_ \ / _` |/ __|
// ___) || |_ | | | || | | || (_| |\__ \
// |____/ \__||_| |_||_| |_| \__, ||___/
// |___/
std::vector<std::string> utils::strings::split(std::string &s, char delim)
{
std::vector<std::string> elems;
std::stringstream ss;
ss.str(s);
std::string item;
while (std::getline(ss, item, delim))
{
elems.push_back(item);
}
return elems;
}
std::string utils::strings::removeSubstrs(std::string &input, std::string pattern)
{
std::string source = input;
std::string::size_type n = pattern.length();
for (std::string::size_type i = source.find(pattern); i != std::string::npos; i = source.find(pattern))
{
source.erase(i, n);
}
return source;
}
您对 initPacket1Length
和 initPacket2Length
的计算是错误的,因此它们分别设置为 4 而不是 3 和 10。这是因为您正在将 char*
指针传递给 sizeof
而不是 char[]
缓冲区,就像您期望的那样(任何指针类型的 sizeof()
在 32 位系统上都是 4,而 8在 64 位系统上)。
这意味着第一个数据包发送的是 4 个字节而不是 3 个字节,但是您没有分配 4 个字节,因此您在第 4 个字节中发送垃圾(幸运的是您的代码没有完全崩溃)。由于 SOCKS 服务器最初只需要 3 个字节,因此第 4 个字节和随后的 4 个字节被解释为失败的格式错误的 5 字节命令。
由于您在分配缓冲区时使用了硬编码大小,因此您应该只对长度进行硬编码以匹配。
//int initPacket1Length = sizeof(initPacket1) / sizeof(initPacket1[0]);
int initPacket1Length = 3;
//int initPacket2Length = sizeof(initPacket2) / sizeof(initPacket2[0]);
int initPacket2Length = 10;
此外,当 memset()
固定缓冲区时,使用 strlen()
来计算大小是完全错误的。您应该改用 sizeof
。
//memset( &reply1, '[=12=]' , strlen((const char *)reply1) );
memset( &reply1, '[=12=]' , sizeof(reply1));
您还忽略了 send()
和 recv()
的 return 值。它们 return 实际的字节数 sent/received。但是您只是检查 return 值是否失败,而不是成功。 TCP 是一种流式传输,send()
和 recv()
可以 send/receive 比请求的字节少,所以你必须考虑到这一点。
话虽这么说,但试试更像这样的东西:
#include <winsock2.h>
#include <string>
#include <iostream>
#include "util.hpp"
using namespace std;
int sendData(SOCKET s, const void *buffer, int buflen)
{
const char *pbuf = (const char*) buffer;
while (buflen > 0)
{
int numSent = send(s, pbuf, buflen, 0);
if (numSent == SOCKET_ERROR)
return SOCKET_ERROR;
pbuf += numSent;
buflen -= numSent;
}
return 1;
}
int recvData(SOCKET s, void *buffer, int buflen)
{
char *pbuf = (char*) buffer;
while (buflen > 0)
{
int numRecv = recv(s, pbuf, buflen, 0);
if (numRecv == SOCKET_ERROR)
return SOCKET_ERROR;
if (numRecv == 0)
return 0;
pbuf += numRecv;
buflen -= numRecv;
}
return 1;
}
int main(void)
{
//SOCKS5 info
u_short sockPort = 45554;
std::string sockIp = "xx.xx.xx.xx";
//Destination info
u_short destPort = 80;
std::string destIPorHost = "checkip.dyndns.com";
//Check to see if winsock2 is available
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,0), &wsaData) != 0)
{
cout << "WSA Startup Failed";
return -1;
}
if (LOBYTE(wsaData.wVersion) < 2)
{
cout << "WSA Version error!";
return -1;
}
///////////////////////////////////////////////////////////////////////////////////////////
//Init sockaddr for SOCKS5
cout << "Initialize sock_addr for socks5 connection...";
sockaddr_in sock;
sock.sin_family = AF_INET; // host byte order
sock.sin_port = htons(sockPort); // short, network byte order
if (!utils::getHostIP(sock.sin_addr.S_un.S_addr, sockIp)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
cout << "done" << endl;
/////////////////////////////////////////////////////////////////////////////////////////////
//Init sockaddr for destination server
cout << "Initialize sockaddr for destination server...";
sockaddr_in dest = {0};
dest.sin_family = AF_INET;
dest.sin_port = htons(destPort);
if (!utils::getHostIP(dest.sin_addr.S_un.S_addr, destIPorHost)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
cout << "done" << endl;
////////////////////////////////////////////////////////////////////////////////////////////////
//Time to connect to SOCKS5 server
//Creating socket handler
cout << "Creating socket handler...";
SOCKET hSocketSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (hSocketSock == INVALID_SOCKET)
{
cout << "fail";
return -1;
}
cout << "done" << endl;
cout << "Connecting to socks server...";
if (connect(hSocketSock, reinterpret_cast<sockaddr *>(&sock), sizeof(sock)) != 0)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
//Documentation: https://tools.ietf.org/html/rfc1928
//We have to send first packet which tell to SOCKS5 to enter on
//sub-negociation mode so we can connect to actual destination server
// The client connects to the server, and sends a version
// identifier/method selection message:
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
//Allocate space for the first initialize packet and his replay
char initPacket1[3];
initPacket1[0] = 5; //SOCKS Version. [VER]
initPacket1[1] = 1; //No. of methods [NMETHODS]
initPacket1[2] = 0; //No auth required [X’00’]
//Now we are sending the packet we just created
cout << "Sending first init packet...";
if (sendData(hSocketSock, initPacket1, 3) < 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//And our expected replay format:
//
// The server selects from one of the methods given in METHODS, and
// sends a METHOD selection message:
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
//Receiving response from server
char reply1[2];
cout << "Waiting for response...";
if (recvData(hSocketSock, reply1, 2) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//reply[0] = our version
//reply[1] = out method. [X’00’ NO AUTHENTICATION REQUIRED]
if( !(reply1[0] == 5 && reply1[1] == 0) )
{
cout << "Error: bad result on reply:" << (int)reply1[0] << " " << (int)reply1[1] << endl;
closesocket(hSocketSock);
return -1;
}
//We have to build initialize packet. This will transmit to SOCKS5 server
//the web server we want to connect to.
//
// The SOCKS request is formed as follows:
//
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// Where:
// 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
//Building that packet
char initPacket2[10];
initPacket2[0] = 5; //SOCKS Version;
initPacket2[1] = 1; //1 = CONNECT, 2 = BIND, 3 = UDP ASSOCIATE;
initPacket2[2] = 0; //Reserved byte
initPacket2[3] = 1; //1 = IPv4, 3 = DOMAINNAME, 4 = IPv6
memcpy(&initPacket2[4], &dest.sin_addr.S_un.S_addr, 4);
memcpy(&initPacket2[8], &dest.sin_port, 2);
//Send the second init packet to server. This will inform the SOCKS5 server
//what is our target.
cout << "Sending second init packet...";
if (sendData(hSocketSock, initPacket2, 10) < 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//Reading the response
//Expected response format:
// The SOCKS request information is sent by the client as soon as it has
// established a connection to the SOCKS server, and completed the
// authentication negotiations. The server evaluates the request, and
// returns a reply formed as follows:
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// Where:
// 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
// ......................................
char reply2[10];
cout << "Waiting for response...";
if (recvData(hSocketSock, reply2, 10) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
cout << "Returned packets: " << endl;
cout << "VER: " << (int)reply2[0] << endl;
cout << "REP: " << (int)reply2[1] << endl;
cout << "RSV: " << (int)reply2[2] << endl;
cout << "ATY: " << (int)reply2[3] << endl;
cout << endl;
// use hSocketSock as needed for subsequent traffic...
closesocket(hSocketSock);
return 0;
}
此外,为什么要从主机名中删除 www.
?这是实际主机名的一部分,不应删除。它确实有所不同,因为 www.domain.com
可能会解析到与 domain.com
.
不同的 IP 地址
您可以通过让 SOCKS 服务器为您解析主机名来稍微简化应用的开销。有时终端客户端解析的 IP 与代理解析的 IP 不同,这取决于代理在网络中的位置。由于代理是与目标主机建立实际连接的代理,因此它应该是解析 IP 的代理:
#include <winsock2.h>
#include <string>
#include <iostream>
#include "util.hpp"
using namespace std;
int sendData(SOCKET s, const void *buffer, int buflen)
{
const char *pbuf = (const char*) buffer;
while (buflen > 0)
{
int numSent = send(s, pbuf, buflen, 0);
if (numSent == SOCKET_ERROR)
return SOCKET_ERROR;
pbuf += numSent;
buflen -= numSent;
}
return 1;
}
int recvData(SOCKET s, void *buffer, int buflen)
{
char *pbuf = (char*) buffer;
while (buflen > 0)
{
int numRecv = recv(s, pbuf, buflen, 0);
if (numRecv == SOCKET_ERROR)
return SOCKET_ERROR;
if (numRecv == 0)
return 0;
pbuf += numRecv;
buflen -= numRecv;
}
return 1;
}
int main(void)
{
//SOCKS5 info
u_short sockPort = 45554;
std::string sockIp = "xx.xx.xx.xx";
//Destination info
u_short destPort = 80;
std::string destIPorHost = "checkip.dyndns.com";
//Check to see if winsock2 is available
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,0), &wsaData) != 0)
{
cout << "WSA Startup Failed";
return -1;
}
if (LOBYTE(wsaData.wVersion) < 2)
{
cout << "WSA Version error!";
return -1;
}
///////////////////////////////////////////////////////////////////////////////////////////
//Init sockaddr for SOCKS5
cout << "Initialize sock_addr for socks5 connection...";
sockaddr_in sock;
sock.sin_family = AF_INET; // host byte order
sock.sin_port = htons(sockPort); // short, network byte order
if (!utils::getHostIP(sock.sin_addr.S_un.S_addr, sockIp)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
cout << "done" << endl;
////////////////////////////////////////////////////////////////////////////////////////////////
//Time to connect to SOCKS5 server
//Creating socket handler
cout << "Creating socket handler...";
SOCKET hSocketSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (hSocketSock == INVALID_SOCKET)
{
cout << "fail";
return -1;
}
cout << "done" << endl;
cout << "Connecting to socks server...";
if (connect(hSocketSock, reinterpret_cast<sockaddr *>(&sock), sizeof(sock)) != 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//Documentation: https://tools.ietf.org/html/rfc1928
//We have to send first packet which tell to SOCKS5 to enter on
//sub-negociation mode so we can connect to actual destination server
// The client connects to the server, and sends a version
// identifier/method selection message:
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
//Allocate space for the first initialize packet and his replay
char initPacket1[3];
initPacket1[0] = 5; //SOCKS Version. [VER]
initPacket1[1] = 1; //No. of methods [NMETHODS]
initPacket1[2] = 0; //No auth required [X’00’]
//Now we are sending the packet we just created
cout << "Sending first init packet...";
if (sendData(hSocketSock, initPacket1, 3) < 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//And our expected replay format:
//
// The server selects from one of the methods given in METHODS, and
// sends a METHOD selection message:
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
//Receiving response from server
char reply1[2];
cout << "Waiting for response...";
if (recvData(hSocketSock, reply1, 2) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//reply[0] = our version
//reply[1] = out method. [X’00’ NO AUTHENTICATION REQUIRED]
if( !(reply1[0] == 5 && reply1[1] == 0) )
{
cout << "Error: bad result on reply:" << (int)reply1[0] << " " << (int)reply1[1] << endl;
closesocket(hSocketSock);
return -1;
}
//We have to build initialize packet. This will transmit to SOCKS5 server
//the web server we want to connect to.
//
// The SOCKS request is formed as follows:
//
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// Where:
// 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
int hostlen = max(destIPorHost.size(), 255);
//Building that packet
char *initPacket2 = new char[7+hostlen];
initPacket2[0] = 5; //SOCKS Version;
initPacket2[1] = 1; //1 = CONNECT, 2 = BIND, 3 = UDP ASSOCIATE;
initPacket2[2] = 0; //Reserved byte
initPacket2[3] = 3; //1 = IPv4, 3 = DOMAINNAME, 4 = IPv6
initPacket2[4] = (char) hostlen;
memcpy(&initPacket2[5], destIPorHost.c_str(), hostlen);
*((u_short*) &(initPacket2[5+hostlen])) = htons(destPort);
//Send the second init packet to server. This will inform the SOCKS5 server
//what is our target.
cout << "Sending second init packet...";
if (sendData(hSocketSock, initPacket2, 7+hostlen) < 0)
{
cout << "failed";
delete[] initPacket2;
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
delete[] initPacket2;
//Reading the response
//Expected response format:
// The SOCKS request information is sent by the client as soon as it has
// established a connection to the SOCKS server, and completed the
// authentication negotiations. The server evaluates the request, and
// returns a reply formed as follows:
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// Where:
// 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
// ......................................
char reply2[262];
cout << "Waiting for response...";
if (recvData(hSocketSock, reply2, 4) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
if (!(reply2[0] == 5 && reply2[1] == 0))
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
int replylen = 4;
switch (reply2[3])
{
case 1:
{
if (recvData(hSocketSock, &reply2[replylen], 4) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
replylen += 4;
break;
}
case 3:
{
if (recvData(hSocketSock, &reply2[replylen], 1) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
hostlen = reply2[replylen];
++replylen;
if (recvData(hSocketSock, &reply2[replylen], hostlen) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
replylen += hostlen;
break;
}
case 4:
{
if (recvData(hSocketSock, &reply2[replylen], 16) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
replylen += 16;
break;
}
}
if (recvData(hSocketSock, &reply2[replylen], 2) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
// use hSocketSock as needed for subsequent traffic...
closesocket(hSocketSock);
return 0;
}
正如标题所说,我想使用Winsock2通过SOCKS5代理服务器下载一个网站源。该程序使用 MinGW 编译。
使用的文档来自RFC 1928。
为了向您介绍这个主题,为了在 SOCKS5 服务器和网络服务器之间建立连接,我必须向 socks 服务器发送两个请求。如果这两个都成功,那么我可以使用带有 send() 和 recv() 的套接字句柄通过我们刚刚创建的隧道与网络服务器交互。
根据 RFC,第一个数据包 看起来像:
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
我只想要一种方法,一种没有授权的方法。 我用这种方式构建了数据包:
char *initPacket1 = new char[3];
int initPacket1Length = sizeof(initPacket1) / sizeof(initPacket1[0]);
initPacket1[0] = 5; //SOCKS Version. [VER]
initPacket1[1] = 1; //No. of methods [NMETHODS]
initPacket1[2] = 0; //No auth required [X’00’]
if(send(hSocketSock, initPacket1, initPacket1Length, 0) == SOCKET_ERROR)
{
return -1;
}
响应应该是一个包含版本和方法的 2 字节数据包。 第一个问题 是这一步有时会失败。更具体地说,recv() 函数有效,但接收到的字节无效。但它只发生过几次。
接下来,我必须构建 第二个数据包 告诉代理服务器连接到目标网站。我必须构建的数据包具有以下形式:
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X’00’ | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
以及我构建数据包的方式:
char *initPacket2 = new char[10];
int initPacket2Length = sizeof(initPacket2) / sizeof(initPacket2[0]);
initPacket2[0] = 5; //SOCKS Version;
initPacket2[1] = 1; //1 = CONNECT, 2 = BIND, 3 = UDP ASSOCIATE;
initPacket2[2] = 0; //Reserved byte
initPacket2[3] = 1; //1 = IPv4, 3 = DOMAINNAME, 4 = IPv6
memcpy(initPacket2 + 4, &dest.sin_addr.S_un.S_addr, 4);
memcpy(initPacket2 + 8, &dest.sin_port, 2);
if(send(hSocketSock, initPacket2, initPacket2Length, 0) == SOCKET_ERROR)
{
return -1;
}
第二个大问题是响应字段每次都是“07”,意思是“不支持命令”。
我已经修改了第二个数据包的第二个字节,但没有成功。
很可能我在构建数据包时遗漏了一些东西,但我不知道是什么。
完整节目: main.cpp
#include <winsock2.h>
#include <string>
#include <iostream>
#include "util.hpp"
using namespace std;
int main(void)
{
//SOCKS5 info
u_short sockPort = 45554;
std::string sockIp = "xx.xx.xx.xx";
//Destination info
u_short destPort = 80;
std::string destIPorURL = "checkip.dyndns.com";
//Check to see if winsock2 is available
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,0), &wsaData)==0)
{
if (LOBYTE(wsaData.wVersion) < 2)
{
cout << "WSA Version error!";
return -1;
}
}
else
{
cout << "WSA Startup Failed";
return -1;
}
///////////////////////////////////////////////////////////////////////////////////////////
//Init sockaddr for SOCKS5
cout << "Initialize sock_addr for socks4 connection...";
sockaddr_in sock;
sock.sin_family = AF_INET; // host byte order
sock.sin_port = htons( sockPort ); // short, network byte order
if(!utils::getHostIP(sock.sin_addr.S_un.S_addr, sockIp)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
cout << "done" << endl;
//Creating socket handler
cout << "Creating socket handler...";
SOCKET hSocketSock = INVALID_SOCKET;
if( (hSocketSock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET )
{
cout << "fail";
return -1;
}
cout << "done" << endl;
/////////////////////////////////////////////////////////////////////////////////////////////
//Init sockaddr for destination server
cout << "Initialize sock_addr for destination server...";
sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_port = htons( destPort );
if(!utils::getHostIP(dest.sin_addr.S_un.S_addr, destIPorURL)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
memset( &(dest.sin_zero), '[=14=]', 8 );
cout << "done" << endl;
////////////////////////////////////////////////////////////////////////////////////////////////
//Time to connect to SOCKS5 server
cout << "Connecting to sock server...";
if(connect(hSocketSock, reinterpret_cast<sockaddr *>(&sock), sizeof(sock)) != 0)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
//Documentation: https://tools.ietf.org/pdf/rfc1928.pdf
//We have to send first packet which tell to SOCKS5 to enter on
//sub-negociation mode so we can connect to actual destination server
// The client connects to the server, and sends a version
// identifier/method selection message:
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
//Allocate space for the first initialize packet and his replay
char *initPacket1 = new char[3];
int initPacket1Length = sizeof(initPacket1) / sizeof(initPacket1[0]);
char reply1[2];
memset( &reply1, '[=14=]' , strlen((const char *)reply1) );
initPacket1[0] = 5; //SOCKS Version. [VER]
initPacket1[1] = 1; //No. of methods [NMETHODS]
initPacket1[2] = 0; //No auth required [X’00’]
//Now we are sending the packet we just created
cout << "Sending first init packet...";
if(send(hSocketSock, initPacket1, initPacket1Length, 0) == SOCKET_ERROR)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
//And our expected replay format:
//
// The server selects from one of the methods given in METHODS, and
// sends a METHOD selection message:
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
//Receiving response from server
cout << "Waiting for response...";
if(recv(hSocketSock, reply1, 2, 0) == SOCKET_ERROR)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
//reply[0] = our version
//reply[1] = out method. [X’00’ NO AUTHENTICATION REQUIRED]
if( !(reply1[0] == 5 && reply1[1] == 0) )
{
cout << "Error: bad result on reply:" << (int)reply1[0] << " " << (int)reply1[1] << endl;
return -1;
}
//We can now delete forst packet
delete[] initPacket1;
//We have to build initialize packet. This will transmit to SOCKS5 server
//the web server we want to connect to.
//
// The SOCKS request is formed as follows:
//
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// Where:
// 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
//Building that packet
char *initPacket2 = new char[10];
int initPacket2Length = sizeof(initPacket2) / sizeof(initPacket2[0]);
char reply2[16];
int reply2Length = sizeof(reply2) / sizeof(reply2[0]);
initPacket2[0] = 5; //SOCKS Version;
initPacket2[1] = 1; //1 = CONNECT, 2 = BIND, 3 = UDP ASSOCIATE;
initPacket2[2] = 0; //Reserved byte
initPacket2[3] = 1; //1 = IPv4, 3 = DOMAINNAME, 4 = IPv6
memcpy(initPacket2 + 4, &dest.sin_addr.S_un.S_addr, 4);
memcpy(initPacket2 + 8, &dest.sin_port, 2);
//Send the second init packet to server. This will inform the SOCKS5 server
//what is our target.
cout << "Sending second init packet...";
if(send(hSocketSock, initPacket2, initPacket2Length, 0) == SOCKET_ERROR)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
//Reading the response
//Expected response format:
// The SOCKS request information is sent by the client as soon as it has
// established a connection to the SOCKS server, and completed the
// authentication negotiations. The server evaluates the request, and
// returns a reply formed as follows:
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// Where:
// 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
// ......................................
cout << "Waiting for response...";
if(recv(hSocketSock, reply2, reply2Length, 0) == SOCKET_ERROR)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
cout << "Returned packets: " << endl;
cout << "VER: " << (int)reply2[0] << endl;
cout << "REP: " << (int)reply2[1] << endl;
cout << "RSV: " << (int)reply2[2] << endl;
cout << "ATY: " << (int)reply2[3] << endl;
cout << endl;
}
返回的字节 REP 是 07,但我需要一个 0 才能继续。
我附上了一个包含有用功能的源文件,以备您测试。
util.hpp:
#include <winsock2.h>
#include <string>
#include <algorithm>
#include <vector>
namespace utils
{
std::string getHostFromUrl(std::string &url);
bool getHostIP(unsigned long &ipAddr, std::string urlOrHostnameOrIp);
namespace IPAddr
{
bool isValidIPv4(std::string &ip);
std::string reverseIpAddress(std::string ip);
std::string decimalToDottedIp(unsigned long ip);
unsigned long stripToDecimal(std::string &ip);
}
namespace strings
{
std::vector<std::string> split(std::string &s, char delim);
std::string removeSubstrs(std::string &source, std::string pattern);
}
};
util.cpp:
#include <stdexcept>
#include <iostream>
#include <sstream>
#include <stdio.h>
#include "util.hpp"
#define cout std::cout
#define endl std::endl
/////////////////////////////////////////////////////////////////////////////////////
// _ _ _ _ _
// | \ | | __ _ _ __ ___ ___ ___ _ __ __ _ ___ ___ _ _| |_(_) |___
// | \| |/ _` | '_ ` _ \ / _ \/ __| '_ \ / _` |/ __/ _ \ | | | | __| | / __|
// | |\ | (_| | | | | | | __/\__ \ |_) | (_| | (_| __/ | |_| | |_| | \__ \
// |_| \_|\__,_|_| |_| |_|\___||___/ .__/ \__,_|\___\___| \__,_|\__|_|_|___/
// |_|
/////////////////////////////////////////////////////////////////////////////////////
bool utils::getHostIP(unsigned long &ipAddr, std::string url)
{
HOSTENT *pHostent;
std::string hostname = getHostFromUrl(url);
if( utils::IPAddr::isValidIPv4(hostname) )
{
//IP Address must be reversed in order to be compatible with sockAddr.sin_addr.S_un.S_addr
//example: 192.168.1.2 => 2.1.168.192
hostname = utils::IPAddr::reverseIpAddress(hostname);
ipAddr = utils::IPAddr::stripToDecimal(hostname);
return true;
}
if (!(pHostent = gethostbyname(hostname.c_str())))
{
return false;
}
if (pHostent->h_addr_list && pHostent->h_addr_list[0])
{
ipAddr = *reinterpret_cast<unsigned long *>(pHostent->h_addr_list[0]);
return true;
}
return false;
}
std::string utils::getHostFromUrl(std::string &url)
{
std::string urlcopy = url;
urlcopy = utils::strings::removeSubstrs(urlcopy, "http://");
urlcopy = utils::strings::removeSubstrs(urlcopy, "www.");
urlcopy = utils::strings::removeSubstrs(urlcopy, "https://");
urlcopy = urlcopy.substr(0, urlcopy.find("/"));
return urlcopy;
}
// ___ ____ _ _ _
// | _ _|| _ \ / \ __| | __| | _ __ ___ ___ ___
// | | | |_) | / _ \ / _` | / _` || '__|/ _ \/ __|/ __|
// | | | __/ / ___ \| (_| || (_| || | | __/\__ \__ \
// |___||_| /_/ \_\__,_| \__,_||_| \___||___/|___/
bool utils::IPAddr::isValidIPv4(std::string &ipv4)
{
const std::string address = ipv4;
std::vector<std::string> arr;
int k = 0;
arr.push_back(std::string());
for (std::string::const_iterator i = address.begin(); i != address.end(); ++i)
{
if (*i == '.')
{
++k;
arr.push_back(std::string());
if (k == 4)
{
return false;
}
continue;
}
if (*i >= '0' && *i <= '9')
{
arr[k] += *i;
}
else
{
return false;
}
if (arr[k].size() > 3)
{
return false;
}
}
if (k != 3)
{
return false;
}
for (int i = 0; i != 4; ++i)
{
const char* nPtr = arr[i].c_str();
char* endPtr = 0;
const unsigned long a = ::strtoul(nPtr, &endPtr, 10);
if (nPtr == endPtr)
{
return false;
}
if (a > 255)
{
return false;
}
}
return true;
}
std::string utils::IPAddr::reverseIpAddress(std::string ip)
{
std::vector<std::string> octeti = utils::strings::split(ip, '.');
return (octeti[3] + "." + octeti[2] + "." + octeti[1] + "." + octeti[0]);
}
unsigned long utils::IPAddr::stripToDecimal(std::string &ip)
{
unsigned long a,b,c,d,base10IP;
sscanf(ip.c_str(), "%lu.%lu.%lu.%lu", &a, &b, &c, &d);
// Do calculations to convert IP to base 10
a *= 16777216;
b *= 65536;
c *= 256;
base10IP = a + b + c + d;
return base10IP;
}
std::string utils::IPAddr::decimalToDottedIp(unsigned long ipAddr)
{
unsigned short a, b, c, d;
std::ostringstream os ;
std::string ip = "";
a = (ipAddr & (0xff << 24)) >> 24;
b = (ipAddr & (0xff << 16)) >> 16;
c = (ipAddr & (0xff << 8)) >> 8;
d = ipAddr & 0xff;
os << d << "." << c << "." << b << "." << a;
ip = os.str();
return ip;
}
// ____ _ _
// / ___| | |_ _ __ (_) _ __ __ _ ___
// \___ \ | __|| '__|| || '_ \ / _` |/ __|
// ___) || |_ | | | || | | || (_| |\__ \
// |____/ \__||_| |_||_| |_| \__, ||___/
// |___/
std::vector<std::string> utils::strings::split(std::string &s, char delim)
{
std::vector<std::string> elems;
std::stringstream ss;
ss.str(s);
std::string item;
while (std::getline(ss, item, delim))
{
elems.push_back(item);
}
return elems;
}
std::string utils::strings::removeSubstrs(std::string &input, std::string pattern)
{
std::string source = input;
std::string::size_type n = pattern.length();
for (std::string::size_type i = source.find(pattern); i != std::string::npos; i = source.find(pattern))
{
source.erase(i, n);
}
return source;
}
您对 initPacket1Length
和 initPacket2Length
的计算是错误的,因此它们分别设置为 4 而不是 3 和 10。这是因为您正在将 char*
指针传递给 sizeof
而不是 char[]
缓冲区,就像您期望的那样(任何指针类型的 sizeof()
在 32 位系统上都是 4,而 8在 64 位系统上)。
这意味着第一个数据包发送的是 4 个字节而不是 3 个字节,但是您没有分配 4 个字节,因此您在第 4 个字节中发送垃圾(幸运的是您的代码没有完全崩溃)。由于 SOCKS 服务器最初只需要 3 个字节,因此第 4 个字节和随后的 4 个字节被解释为失败的格式错误的 5 字节命令。
由于您在分配缓冲区时使用了硬编码大小,因此您应该只对长度进行硬编码以匹配。
//int initPacket1Length = sizeof(initPacket1) / sizeof(initPacket1[0]);
int initPacket1Length = 3;
//int initPacket2Length = sizeof(initPacket2) / sizeof(initPacket2[0]);
int initPacket2Length = 10;
此外,当 memset()
固定缓冲区时,使用 strlen()
来计算大小是完全错误的。您应该改用 sizeof
。
//memset( &reply1, '[=12=]' , strlen((const char *)reply1) );
memset( &reply1, '[=12=]' , sizeof(reply1));
您还忽略了 send()
和 recv()
的 return 值。它们 return 实际的字节数 sent/received。但是您只是检查 return 值是否失败,而不是成功。 TCP 是一种流式传输,send()
和 recv()
可以 send/receive 比请求的字节少,所以你必须考虑到这一点。
话虽这么说,但试试更像这样的东西:
#include <winsock2.h>
#include <string>
#include <iostream>
#include "util.hpp"
using namespace std;
int sendData(SOCKET s, const void *buffer, int buflen)
{
const char *pbuf = (const char*) buffer;
while (buflen > 0)
{
int numSent = send(s, pbuf, buflen, 0);
if (numSent == SOCKET_ERROR)
return SOCKET_ERROR;
pbuf += numSent;
buflen -= numSent;
}
return 1;
}
int recvData(SOCKET s, void *buffer, int buflen)
{
char *pbuf = (char*) buffer;
while (buflen > 0)
{
int numRecv = recv(s, pbuf, buflen, 0);
if (numRecv == SOCKET_ERROR)
return SOCKET_ERROR;
if (numRecv == 0)
return 0;
pbuf += numRecv;
buflen -= numRecv;
}
return 1;
}
int main(void)
{
//SOCKS5 info
u_short sockPort = 45554;
std::string sockIp = "xx.xx.xx.xx";
//Destination info
u_short destPort = 80;
std::string destIPorHost = "checkip.dyndns.com";
//Check to see if winsock2 is available
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,0), &wsaData) != 0)
{
cout << "WSA Startup Failed";
return -1;
}
if (LOBYTE(wsaData.wVersion) < 2)
{
cout << "WSA Version error!";
return -1;
}
///////////////////////////////////////////////////////////////////////////////////////////
//Init sockaddr for SOCKS5
cout << "Initialize sock_addr for socks5 connection...";
sockaddr_in sock;
sock.sin_family = AF_INET; // host byte order
sock.sin_port = htons(sockPort); // short, network byte order
if (!utils::getHostIP(sock.sin_addr.S_un.S_addr, sockIp)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
cout << "done" << endl;
/////////////////////////////////////////////////////////////////////////////////////////////
//Init sockaddr for destination server
cout << "Initialize sockaddr for destination server...";
sockaddr_in dest = {0};
dest.sin_family = AF_INET;
dest.sin_port = htons(destPort);
if (!utils::getHostIP(dest.sin_addr.S_un.S_addr, destIPorHost)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
cout << "done" << endl;
////////////////////////////////////////////////////////////////////////////////////////////////
//Time to connect to SOCKS5 server
//Creating socket handler
cout << "Creating socket handler...";
SOCKET hSocketSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (hSocketSock == INVALID_SOCKET)
{
cout << "fail";
return -1;
}
cout << "done" << endl;
cout << "Connecting to socks server...";
if (connect(hSocketSock, reinterpret_cast<sockaddr *>(&sock), sizeof(sock)) != 0)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
//Documentation: https://tools.ietf.org/html/rfc1928
//We have to send first packet which tell to SOCKS5 to enter on
//sub-negociation mode so we can connect to actual destination server
// The client connects to the server, and sends a version
// identifier/method selection message:
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
//Allocate space for the first initialize packet and his replay
char initPacket1[3];
initPacket1[0] = 5; //SOCKS Version. [VER]
initPacket1[1] = 1; //No. of methods [NMETHODS]
initPacket1[2] = 0; //No auth required [X’00’]
//Now we are sending the packet we just created
cout << "Sending first init packet...";
if (sendData(hSocketSock, initPacket1, 3) < 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//And our expected replay format:
//
// The server selects from one of the methods given in METHODS, and
// sends a METHOD selection message:
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
//Receiving response from server
char reply1[2];
cout << "Waiting for response...";
if (recvData(hSocketSock, reply1, 2) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//reply[0] = our version
//reply[1] = out method. [X’00’ NO AUTHENTICATION REQUIRED]
if( !(reply1[0] == 5 && reply1[1] == 0) )
{
cout << "Error: bad result on reply:" << (int)reply1[0] << " " << (int)reply1[1] << endl;
closesocket(hSocketSock);
return -1;
}
//We have to build initialize packet. This will transmit to SOCKS5 server
//the web server we want to connect to.
//
// The SOCKS request is formed as follows:
//
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// Where:
// 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
//Building that packet
char initPacket2[10];
initPacket2[0] = 5; //SOCKS Version;
initPacket2[1] = 1; //1 = CONNECT, 2 = BIND, 3 = UDP ASSOCIATE;
initPacket2[2] = 0; //Reserved byte
initPacket2[3] = 1; //1 = IPv4, 3 = DOMAINNAME, 4 = IPv6
memcpy(&initPacket2[4], &dest.sin_addr.S_un.S_addr, 4);
memcpy(&initPacket2[8], &dest.sin_port, 2);
//Send the second init packet to server. This will inform the SOCKS5 server
//what is our target.
cout << "Sending second init packet...";
if (sendData(hSocketSock, initPacket2, 10) < 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//Reading the response
//Expected response format:
// The SOCKS request information is sent by the client as soon as it has
// established a connection to the SOCKS server, and completed the
// authentication negotiations. The server evaluates the request, and
// returns a reply formed as follows:
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// Where:
// 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
// ......................................
char reply2[10];
cout << "Waiting for response...";
if (recvData(hSocketSock, reply2, 10) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
cout << "Returned packets: " << endl;
cout << "VER: " << (int)reply2[0] << endl;
cout << "REP: " << (int)reply2[1] << endl;
cout << "RSV: " << (int)reply2[2] << endl;
cout << "ATY: " << (int)reply2[3] << endl;
cout << endl;
// use hSocketSock as needed for subsequent traffic...
closesocket(hSocketSock);
return 0;
}
此外,为什么要从主机名中删除 www.
?这是实际主机名的一部分,不应删除。它确实有所不同,因为 www.domain.com
可能会解析到与 domain.com
.
您可以通过让 SOCKS 服务器为您解析主机名来稍微简化应用的开销。有时终端客户端解析的 IP 与代理解析的 IP 不同,这取决于代理在网络中的位置。由于代理是与目标主机建立实际连接的代理,因此它应该是解析 IP 的代理:
#include <winsock2.h>
#include <string>
#include <iostream>
#include "util.hpp"
using namespace std;
int sendData(SOCKET s, const void *buffer, int buflen)
{
const char *pbuf = (const char*) buffer;
while (buflen > 0)
{
int numSent = send(s, pbuf, buflen, 0);
if (numSent == SOCKET_ERROR)
return SOCKET_ERROR;
pbuf += numSent;
buflen -= numSent;
}
return 1;
}
int recvData(SOCKET s, void *buffer, int buflen)
{
char *pbuf = (char*) buffer;
while (buflen > 0)
{
int numRecv = recv(s, pbuf, buflen, 0);
if (numRecv == SOCKET_ERROR)
return SOCKET_ERROR;
if (numRecv == 0)
return 0;
pbuf += numRecv;
buflen -= numRecv;
}
return 1;
}
int main(void)
{
//SOCKS5 info
u_short sockPort = 45554;
std::string sockIp = "xx.xx.xx.xx";
//Destination info
u_short destPort = 80;
std::string destIPorHost = "checkip.dyndns.com";
//Check to see if winsock2 is available
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,0), &wsaData) != 0)
{
cout << "WSA Startup Failed";
return -1;
}
if (LOBYTE(wsaData.wVersion) < 2)
{
cout << "WSA Version error!";
return -1;
}
///////////////////////////////////////////////////////////////////////////////////////////
//Init sockaddr for SOCKS5
cout << "Initialize sock_addr for socks5 connection...";
sockaddr_in sock;
sock.sin_family = AF_INET; // host byte order
sock.sin_port = htons(sockPort); // short, network byte order
if (!utils::getHostIP(sock.sin_addr.S_un.S_addr, sockIp)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
cout << "done" << endl;
////////////////////////////////////////////////////////////////////////////////////////////////
//Time to connect to SOCKS5 server
//Creating socket handler
cout << "Creating socket handler...";
SOCKET hSocketSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (hSocketSock == INVALID_SOCKET)
{
cout << "fail";
return -1;
}
cout << "done" << endl;
cout << "Connecting to socks server...";
if (connect(hSocketSock, reinterpret_cast<sockaddr *>(&sock), sizeof(sock)) != 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//Documentation: https://tools.ietf.org/html/rfc1928
//We have to send first packet which tell to SOCKS5 to enter on
//sub-negociation mode so we can connect to actual destination server
// The client connects to the server, and sends a version
// identifier/method selection message:
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
//Allocate space for the first initialize packet and his replay
char initPacket1[3];
initPacket1[0] = 5; //SOCKS Version. [VER]
initPacket1[1] = 1; //No. of methods [NMETHODS]
initPacket1[2] = 0; //No auth required [X’00’]
//Now we are sending the packet we just created
cout << "Sending first init packet...";
if (sendData(hSocketSock, initPacket1, 3) < 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//And our expected replay format:
//
// The server selects from one of the methods given in METHODS, and
// sends a METHOD selection message:
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
//Receiving response from server
char reply1[2];
cout << "Waiting for response...";
if (recvData(hSocketSock, reply1, 2) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
//reply[0] = our version
//reply[1] = out method. [X’00’ NO AUTHENTICATION REQUIRED]
if( !(reply1[0] == 5 && reply1[1] == 0) )
{
cout << "Error: bad result on reply:" << (int)reply1[0] << " " << (int)reply1[1] << endl;
closesocket(hSocketSock);
return -1;
}
//We have to build initialize packet. This will transmit to SOCKS5 server
//the web server we want to connect to.
//
// The SOCKS request is formed as follows:
//
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// Where:
// 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
int hostlen = max(destIPorHost.size(), 255);
//Building that packet
char *initPacket2 = new char[7+hostlen];
initPacket2[0] = 5; //SOCKS Version;
initPacket2[1] = 1; //1 = CONNECT, 2 = BIND, 3 = UDP ASSOCIATE;
initPacket2[2] = 0; //Reserved byte
initPacket2[3] = 3; //1 = IPv4, 3 = DOMAINNAME, 4 = IPv6
initPacket2[4] = (char) hostlen;
memcpy(&initPacket2[5], destIPorHost.c_str(), hostlen);
*((u_short*) &(initPacket2[5+hostlen])) = htons(destPort);
//Send the second init packet to server. This will inform the SOCKS5 server
//what is our target.
cout << "Sending second init packet...";
if (sendData(hSocketSock, initPacket2, 7+hostlen) < 0)
{
cout << "failed";
delete[] initPacket2;
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
delete[] initPacket2;
//Reading the response
//Expected response format:
// The SOCKS request information is sent by the client as soon as it has
// established a connection to the SOCKS server, and completed the
// authentication negotiations. The server evaluates the request, and
// returns a reply formed as follows:
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X’00’ | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// Where:
// 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
// ......................................
char reply2[262];
cout << "Waiting for response...";
if (recvData(hSocketSock, reply2, 4) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
if (!(reply2[0] == 5 && reply2[1] == 0))
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
int replylen = 4;
switch (reply2[3])
{
case 1:
{
if (recvData(hSocketSock, &reply2[replylen], 4) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
replylen += 4;
break;
}
case 3:
{
if (recvData(hSocketSock, &reply2[replylen], 1) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
hostlen = reply2[replylen];
++replylen;
if (recvData(hSocketSock, &reply2[replylen], hostlen) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
replylen += hostlen;
break;
}
case 4:
{
if (recvData(hSocketSock, &reply2[replylen], 16) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
replylen += 16;
break;
}
}
if (recvData(hSocketSock, &reply2[replylen], 2) <= 0)
{
cout << "failed";
closesocket(hSocketSock);
return -1;
}
cout << "done" << endl;
// use hSocketSock as needed for subsequent traffic...
closesocket(hSocketSock);
return 0;
}