SOCKS5协议解析
socks的官方文档:https://www.ietf.org/rfc/rfc1928.txt
本文改变其他作者之手,在原文基础上加入客户端的编写,完善了服务端代码,原文是Linux端的程序代码,本文改为了Windows端程序代码。以下为原文链接:从零实现加密隧道(二):socks5 协议详解
SOCKS5 是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。SOCKS5 服务器通过将前端发来的请求转发给真正的目标服务器,模拟了一个前端的行为。在这里,前端和SOCKS5之间也是通过TCP/IP协议进行通讯,前端将原本要发送给真正服务器的请求发送给SOCKS5服务器,然后SOCKS5服务器将请求转发给真正的服务器。
注意:本文中程序仅适用于Windows端
一、SOCKS5通信流程
1.客户端与服务器身份验证
2.代理服务器响应客户端请求
3.客户端向代理服务器发送请求地址
4.代理服务器响应客户端请求(代理与远程服务器建立链接,代理服务器相应客户端请求)
5.客户端向代理传输数据
6.代理服务器将数据转发给远程服务器
7.远程服务器将数据发送到代理服务器
8.代理服务器将数据转发给客户端
(一)客户端发送的报头
VERSION | METHODS_COUNT | METHODS |
---|---|---|
1字节 | 1字节 | 1到255字节,长度由METHODS_COUNT值决定 |
0x05 | 0x03 | …… |
VERSION:socks 版本,这里用的是 socks5,所以是0x05。
METHODS_COUNT: METHODS 部分的长度。
METHODS:代表客户端拥有的加密方式。每个方法占 1 字节。当前的定义是:
0x00 不加密
0x01 GSSAPI
0x02 用户名、密码认证
0x03 - 0x7F 由IANA分配(保留)
0x80 - 0xFE 为私人方法保留
0xFF 无可接受的方法
// 客户端认证请求
typedef struct client_license_request {
char ver; // 客户端的协议版本号 0x05:socks5 0x04:socks4
char nmethods; // 客户端所支持认证方式的长度
char methods[255]; // 客户端支持的认证方式(可以有255种)
}client_license_request;
// Client端 -- 发送认证信息
client_license_request license_request;
license_request = {0};
license_request.ver = 0x5;
send_len = send(s_server, (char *)&license_request, sizeof(license_request),0);
if (send_len < 0)
{
cout << "验证认证信息失败!" << endl;
}
// 代理服务器端 -- 接收认证信息
int srlen = 0;
//接收认证信息
char buffer[257];
recv(fd, buffer, sizeof(buffer), 0);
client_license_request * license_request = (client_license_request *)buffer;
//验证认证信息
printf("客户端版本%d\n", license_request->ver);
if (license_request->ver != 0x5)
{
printf("协议版本错误\n");
return 0;
}
(二)代理服务器响应的报头
VERSION | METHODS |
---|---|
1字节 | 1字节 |
0x05 | 从客户端发送的加密方式里面选一个 |
VERSION:socks 版本,这里用的是 socks5,所以是0x05。
METHODS:代表代理服务器选择了一种握手方式。占 1 字节。
例如,代理服务器发送的 5 0,代表 版本5 选择了“不加密”的握手方式。
如果客户端的所有握手方式代理服务器都不满足,直接断开连接。
如果代理服务器发送 5 2,代表 版本5 选择了“用户名、密码认证”的握手方式。此时客户端会发送账号密码数据给代理服务器,再由代理服务器检验,并返回结果。格式如下:
VERSION | USERNAME_LENGTH | USERNAME | PASSWORD_LENGTH | PASSWORD |
---|---|---|---|---|
1字节 | 1字节 | 1-255字节 | 1字节 | 1-255字节 |
0x01 | 0x01 | …… | 0x01 | …… |
VERSION:认证子协商版本(与 SOCKS 协议版本的0x05无关系)
USERNAME_LENGTH:用户名长度
USERNAME:用户名字节数组,长度为 USERNAME_LENGTH
PASSWORD_LENGTH:密码长度
PASSWORD:密码字节数组,长度为 PASSWORD_LENGTH
VERSION | USERNAME_LENGTH |
---|---|
1字节 | 1字节 |
0x01 | 0x01 |
VERSION:认证子协商版本,与客户端 VERSION 字段一致
STATUS:认证结果(0x00 认证成功 / 大于0x00 认证失败)
// 服务端回应认证
typedef struct server_license_response {
char ver; // 服务端的协议版本号
char method; // 服务端选择的认证方式
}server_license_response;
// 代理服务器端 -- 响应认证信息
server_license_response license_response;
license_response.ver = 0x5;
license_response.method = 0x0;
char buff[2] = { 0 };
memcpy(buff, &license_response, sizeof(buff));
//回应认证信息
srlen = send(fd, buff, sizeof(buff), 0);
if (srlen <= 0)
{
}
printf("已发送回应请求\n");
// Client端 -- 接收代理服务器的回应
server_license_response license_response;
license_response = { 0 };
recv(s_server,(char*)&license_response, sizeof(license_response), 0);
if (license_response.ver != 0x5 || license_response.method != 0x0)
{
cout << "代理服务器回应认证失败!" << endl;
}
(三)客户端发送需要访问的IP和端口,以及协议
VERSION | COMMAND | RSV | ADDRESS_TYPE | DST.ADDR | DST.PORT |
---|---|---|---|---|---|
1字节 | 1字节 | 1字节 | 1字节 | 可变成长度 | 2字节 |
VERSION:SOCKS 协议版本,固定 0x05
COMMAND:命令
0x01:CONNECT请求,连接上游服务器(使用TCP)
0x02:BIND 绑定,客户端会接收来自代理服务器的链接,著名的FTP被动模式
0x03:UDP ASSOCIATE UDP 中继(UDP 转发)
RSV:保留字段,无实际作用
ADDRESS_TYPE:目标服务器地址类型
0x01:表示 IPv4 地址
0x03:域名地址(没有打错,就是没有0x02)
0x04:IPv6 地址
DST.ADDR:目标服务器地址(如果是ipv6,该字段的第一个字节是域名长度,剩下字节为域名)
DST.PORT:目标服务器端口
// 客户端连接请求
#pragma pack(1)
typedef struct client_connect_request {
char ver; //客户端协议版本号
char cmd; //连接方式
char rsv = 0x00; //保留位0x00
char type; //类型
char addr[20] = "10.18.33.21"; //目的服务器ip
char port[6] = "2019"; //目的服务器端口
}client_connect_request;
// Client端 -- 向代理服务器发送请求
client_connect_request connect_request;
connect_request.ver = 0x5;
connect_request.cmd = 0x1;
connect_request.type = 0x01;
send_len = send(s_server,(char *)&connect_request, sizeof(client_connect_request) , 0);
if (send_len < 0)
{
cout << "向代理服务器发送请求失败!" << endl;
}
(四)代理服务器响应
VERSION | RESPONSE | RSV | ADDRESS_TYPE | BND.ADDR | BND.PORT |
---|---|---|---|---|---|
1字节 | 1字节 | 1字节 | 1字节 | 1-255字节 | 2字节 |
VERSION:SOCKS 协议版本,固定 0x05
RESPONSE:响应命令
0x00:代理服务器连接目标服务器成功
0x01:代理服务器故障
0x02:代理服务器规则集不允许连接
0x03:网络无法访问
0x04:目标服务器无法访问(主机名无效)
0x05:连接目标服务器被拒绝
0x06:TTL已过期
0x07:不支持的命令
0x08:不支持的目标服务器地址类型
0x09 - 0xFF:未分配
RSV:保留字段
ADDRESS_TYPE:后面的地址类型
0x01:ipv4
0x03:域名
0x04:ipv6
BND.ADDR:代理服务器连接目标服务器成功后的代理服务器 IP
BND.PORT:代理服务器连接目标服务器成功后的代理服务器端口
// 代理服务器端 -- 接收IP与PORT,链接目标机,发回成功信息给 Client
char buf[4096];
srlen = recv(fd, buf, 4, 0); // 03 05 00 01
if (srlen <= 0)
{
}
if (srlen <= 0) return -1;
if (srlen < 4) return 0;
if (buf[0] != 0x05 || buf[2] != 0x00)
{
}
int client_fd = 0;
char ip4[256], port[5];
int re = -1;
if (buf[3] == 0x04)
{ // 如果是 ipv6
// ...
return 0;
}
else if (buf[3] == 0x01) { // 如果是 ipv4
srlen = recv(fd, ip4, 4, 0);
srlen = recv(fd, port, 2, 0);
ip4[4] = '\0';
port[2] = '\0';
client_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server;
server.sin_family = AF_INET;
memcpy(&server.sin_addr.s_addr, ip4, 4);
server.sin_port = *((uint16_t*)port);
re = connect(client_fd, (struct sockaddr*)&server, sizeof(server));
if (re == -1)
{
}
// ...
}
else if (buf[3] == 0x03) { // 是用域名表示的
// 域名字段中第一个字节是真实的域名的长度,后面才是真实的域名
char doname_len;
char doname[256];
srlen = recv(fd, &doname_len, 1, 0);
if (len < 1) return 0;
len = recv(fd, doname, doname_len, 0);
doname[len] = '\0';
struct hostent* host = gethostbyname(doname);
if (host != nullptr)
{
memcpy(ip4, host->h_addr, host->h_length);
len = recv(fd, port, 2, 0);
client_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server;
server.sin_family = AF_INET;
memcpy(&server.sin_addr.s_addr, ip4, 4);
server.sin_port = *((uint16_t*)port);
re = connect(client_fd, (struct sockaddr*)&server, sizeof(server));
if (re == -1)
{
}
}
}
else
{
}
//成功连接则发送回应信息
//回应连接信息
char buffer1[10] = { 0 };
server_connect_response connect_response = { 0 };
connect_response.ver = 0x5;
connect_response.rep = 0x00; //连接成功标志
connect_response.rsv = 0x00;
connect_response.type = 0x01;
memcpy(buffer1, &connect_response, sizeof(connect_response));//服务端回应数据 设置版本号与结果位,ip与端口号未使用
send(fd, buffer1, sizeof(buffer1), 0);
printf("已发送回应请求\n");
if (client_fd != 0 && re != -1)
{
ForwardData(fd, client_fd);
}
// Client -- 接收代理服务器链接消息是否成功
server_connect_response connect_response;
connect_response = { 0 };
recv_len = recv(s_server, (char*)&connect_response, sizeof(connect_response), 0);
if (recv_len < 0)
{
cout << "代理服务器接收请求失败!" << endl;
}
else if(connect_response.ver != 0x5 || connect_response.rep != 0x00 || connect_response.rsv != 0x00 || connect_response.type != 0x01)
{
cout << "代理服务器接收请求失败!" << endl;
}
else
{
cout << "向代理服务器发送请求成功!" << endl;
}
(五)通信
// Client端 -- 测试:发送数据给代理服务器,再接收代理服务器发送的数据
char recv_buffer[BUFF_SIZE];
char tmp[] = "Hello";
memcpy(recv_buffer, tmp, sizeof(tmp)+1);
send_len = send(s_server, (char *)&recv_buffer, sizeof(recv_buffer), 0);
char buff3[100];
recv_len = recv(s_server, (char*)&buff3, sizeof(buff3), 0);
printf(buff3);
//代理服务器进行数据转发
int ForwardData(int sock, int real_server_sock) // sock:链接Client的socket; real_server_sock:链接内网的socket
{
char recv_buffer[4096] = { 0 };
fd_set fd_read;
struct timeval time_out;
time_out.tv_sec = 0;
time_out.tv_usec = TIME_OUT;
int ret = 0;
printf("线程%u-开始进行数据转发\n", (int)GetCurrentThread());
fd_set fd_read2;
int ret2;
while (1)
{
FD_ZERO(&fd_read);
FD_SET(sock, &fd_read);
FD_SET(real_server_sock, &fd_read);
ret = select((sock < real_server_sock ? sock : real_server_sock) + 1, &fd_read, NULL, NULL, &time_out); //&time_out
if (-1 == ret)
{
break;
}
else if (0 == ret)
{
continue;
}
if (FD_ISSET(sock, &fd_read))
{
memset(recv_buffer, 0, 4096);
ret = recv(sock, recv_buffer, 4096, 0);
if (ret > 0)
{
printf("从浏览器 %d 接收数据\r\n", sock);
ret = send(real_server_sock, recv_buffer, ret, 0);
if (ret == -1)
{
break;
}
printf("向内网 %d 发送数据\r\n", real_server_sock);
}
else if (ret == 0)
{
break;
}
else
{
break;
}
}
else if (FD_ISSET(real_server_sock, &fd_read))
{
memset(recv_buffer, 0, 4096);
ret = recv(real_server_sock, recv_buffer, 4096, 0);
if (ret > 0)
{
printf("从内网 %d 接收数据\r\n", real_server_sock);
ret = send(sock, recv_buffer, ret, 0);
if (ret == -1)
{
break;
}
printf("向浏览器 %d 发送数据\r\n", sock);
}
else if (ret == 0)
{
break;
}
else
{
break;
}
}
}
return 0;
}
二、源代码
注意:本文中程序仅适用于Windows端
// 代理服务器端
#include<iostream>
#include<winsock.h>
#pragma comment(lib,"ws2_32.lib")
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define BUFF_SIZE 1024 // 设置转发缓冲区
#define TIME_OUT 6000000 // 设置复用IO延时
unsigned int __g1 = 0;
CRITICAL_SECTION __CriticalSection;
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
//++++++++++++ sock5协议结构体定义 ++++++++++++++
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
/*
一、客户端认证请求
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1~255 |
+----+----------+----------+
二、服务端回应认证
+----+--------+
|VER | METHOD |
+----+--------+
| 1 | 1 |
+----+--------+
三、客户端连接请求(连接目的网络)
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | 1 | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
四、服务端回应连接
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | 1 | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
*/
//以下为协议结构体定义
//一、客户端认证请求
typedef struct client_license_request {
char ver; // 客户端的协议版本号 0x05:socks5 0x04:socks4
char nmethods; // 客户端所支持认证方式的长度
char methods[255]; //客户端支持的认证方式(可以有255种)
}client_license_request;
//二、服务端回应认证
typedef struct server_license_response {
char ver; // 服务端的协议版本号
char method; //服务端选择的认证方式
}server_license_response;
//三、客户端连接请求
typedef struct client_connect_request {
char ver; //客户端协议版本号
char cmd; //连接方式
char rsv; //保留位0x00
char type; //类型
char addr[20]; //目的服务器ip
char port[6]; //目的服务器端口
}client_connect_request;
//四、服务端回应连接
typedef struct server_connect_response {
char ver; //版本
char rep; //连接状态
char rsv; //保留0x00
char type; //类型
char addr[4]; //bind ip
char port[2]; //bind port
}server_connect_response;
int socketfd_tcp; //TCP监听套接字
struct thread_parameter
{
struct sockaddr_in *addr_client;
int len;
int fd;
};
//代理服务器进行数据转发
int ForwardData(int sock, int real_server_sock) // sock:链接Client的socket; real_server_sock:链接内网的socket
{
char recv_buffer[4096] = { 0 };
fd_set fd_read;
struct timeval time_out;
time_out.tv_sec = 0;
time_out.tv_usec = TIME_OUT;
int ret = 0;
printf("线程%u-开始进行数据转发\n", (int)GetCurrentThread());
fd_set fd_read2;
int ret2;
while (1)
{
FD_ZERO(&fd_read);
FD_SET(sock, &fd_read);
FD_SET(real_server_sock, &fd_read);
ret = select((sock < real_server_sock ? sock : real_server_sock) + 1, &fd_read, NULL, NULL, &time_out); //&time_out
if (-1 == ret)
{
break;
}
else if (0 == ret)
{
continue;
}
if (FD_ISSET(sock, &fd_read))
{
memset(recv_buffer, 0, 4096);
ret = recv(sock, recv_buffer, 4096, 0);
if (ret > 0)
{
printf("从浏览器 %d 接收数据\r\n", sock);
ret = send(real_server_sock, recv_buffer, ret, 0);
if (ret == -1)
{
break;
}
printf("向内网 %d 发送数据\r\n", real_server_sock);
}
else if (ret == 0)
{
break;
}
else
{
break;
}
}
else if (FD_ISSET(real_server_sock, &fd_read))
{
memset(recv_buffer, 0, 4096);
ret = recv(real_server_sock, recv_buffer, 4096, 0);
if (ret > 0)
{
printf("从内网 %d 接收数据\r\n", real_server_sock);
ret = send(sock, recv_buffer, ret, 0);
if (ret == -1)
{
break;
}
printf("向浏览器 %d 发送数据\r\n", sock);
}
else if (ret == 0)
{
break;
}
else
{
break;
}
}
}
return 0;
}
// 一、初始化套接字库(WSAStartup)
void initialization()
{
WORD w_req = MAKEWORD(2, 2);// 版本号
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata); // WSAStartup 函数启动进程对 Winsock DLL的使用
if (err != 0)
{
std::cout << "初始化套接字库失败!" << std::endl;
}
else {
std::cout << "初始化套接字库成功!" << std::endl;
}
// 检测版本号
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
std::cout << "套接字库版本号不符!" << std::endl;
WSACleanup();
}
else {
std::cout << "套接字库版本正确!" << std::endl;
}
}
//创建TCP套接字
void tcp_creat()
{
socketfd_tcp = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd_tcp == -1)
{
perror("socketfd_tcp");
exit(-1);
}
struct sockaddr_in addr_tcp = {0};
//bzero(&addr_tcp, sizeof(addr_tcp));
addr_tcp.sin_family = AF_INET;
addr_tcp.sin_port = htons(80);
addr_tcp.sin_addr.s_addr = INADDR_ANY;
int re = bind(socketfd_tcp, (struct sockaddr *)&addr_tcp, sizeof(addr_tcp));
if (re == -1)
{
perror("bind");
exit(-1);
}
re = listen(socketfd_tcp, 100); //队列长度设为100
if (re == -1)
{
perror("listen");
exit(-1);
}
}
//代理服务器连接目的服务器
int connect_dest_server(client_connect_request * connect_request)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
perror("socketfd_tcp");
return -1;
}
struct sockaddr_in sin_server = { 0 };
//bzero(&sin_server, sizeof(sin_server));
sin_server.sin_family = AF_INET;
sin_server.sin_addr.S_un.S_addr = inet_addr(connect_request->addr);
u_short port = atoi(connect_request->port);
sin_server.sin_port = htons(port);
/*2 连接服务器*/
int re = connect(fd, (struct sockaddr *)&sin_server, sizeof(sin_server));
if (re == -1)
{
// printf("目的服务器连接失败\n");
return -1;
}
// printf("目的服务器连接成功\n");
return fd;
}
//socks5认证连接
int sock5_license(struct sockaddr_in *addr_client, int len, int fd)
{
int srlen = 0;
//接收认证信息
char buffer[257];
recv(fd, buffer, sizeof(buffer), 0);
client_license_request * license_request = (client_license_request *)buffer;
//验证认证信息
printf("客户端版本%d\n", license_request->ver);
if (license_request->ver != 0x5)
{
printf("协议版本错误\n");
return 0;
}
printf("客户认证信息通过,回应认证请求\n");
server_license_response license_response;
license_response.ver = 0x5;
license_response.method = 0x0;
char buff[2] = { 0 };
memcpy(buff, &license_response, sizeof(buff));
//回应认证信息
srlen = send(fd, buff, sizeof(buff), 0);
if (srlen <= 0)
{
}
printf("已发送回应请求\n");
char buf[4096];
srlen = recv(fd, buf, 4, 0); // 03 05 00 01
if (srlen <= 0)
{
}
if (srlen <= 0) return -1;
if (srlen < 4) return 0;
if (buf[0] != 0x05 || buf[2] != 0x00)
{
}
int client_fd = 0;
char ip4[256], port[5];
int re = -1;
if (buf[3] == 0x04)
{ // 如果是 ipv6
// ...
return 0;
}
else if (buf[3] == 0x01) { // 如果是 ipv4
srlen = recv(fd, ip4, 4, 0);
srlen = recv(fd, port, 2, 0);
ip4[4] = '\0';
port[2] = '\0';
client_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server;
server.sin_family = AF_INET;
memcpy(&server.sin_addr.s_addr, ip4, 4);
server.sin_port = *((uint16_t*)port);
re = connect(client_fd, (struct sockaddr*)&server, sizeof(server));
if (re == -1)
{
}
// ...
}
else if (buf[3] == 0x03) { // 是用域名表示的
// 域名字段中第一个字节是真实的域名的长度,后面才是真实的域名
char doname_len;
char doname[256];
srlen = recv(fd, &doname_len, 1, 0);
if (len < 1) return 0;
len = recv(fd, doname, doname_len, 0);
doname[len] = '\0';
struct hostent* host = gethostbyname(doname);
if (host != nullptr)
{
memcpy(ip4, host->h_addr, host->h_length);
len = recv(fd, port, 2, 0);
client_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server;
server.sin_family = AF_INET;
memcpy(&server.sin_addr.s_addr, ip4, 4);
server.sin_port = *((uint16_t*)port);
re = connect(client_fd, (struct sockaddr*)&server, sizeof(server));
if (re == -1)
{
}
}
}
else
{
}
//成功连接则发送回应信息
//回应连接信息
char buffer1[10] = { 0 };
server_connect_response connect_response = { 0 };
connect_response.ver = 0x5;
connect_response.rep = 0x00; //连接成功标志
connect_response.rsv = 0x00;
connect_response.type = 0x01;
memcpy(buffer1, &connect_response, sizeof(connect_response));//服务端回应数据 设置版本号与结果位,ip与端口号未使用
send(fd, buffer1, sizeof(buffer1), 0);
printf("已发送回应请求\n");
if (client_fd != 0 && re != -1)
{
ForwardData(fd, client_fd);
}
}
// 等待TCP连接,每个客户端分出一条线程,跳转执行socks5认证函数↑↑↑
DWORD WINAPI pthread_tcp(LPVOID ParameterData)
{
thread_parameter parameter = *(thread_parameter *)ParameterData;
//打印客户端信息
//char ip[20] = { 0 };
//unsigned short port;
////inet_ntop(AF_INET, &addr_client.sin_addr, ip, len);
//port = ntohs(addr_client.sin_port); //转换为本机字节序
//printf("%s:%hu已连接\n", ip, port);
//执行socks5认证
sock5_license(parameter.addr_client, parameter.len, parameter.fd);
printf("线程%d-退出\n", (HANDLE)GetCurrentThread());
return NULL;
}
int main(void)
{
// 一、初始化套接字库(WSAStartup)
initialization();
//创建TCP套接字
tcp_creat();
printf("初始化完成等待连接\n");
while (1)
{
printf("线程%d-正在运行\n", (HANDLE)GetCurrentThread());
struct sockaddr_in addr_client = { 0 };
int len = sizeof(addr_client);
int fd = accept(socketfd_tcp, (struct sockaddr *)&addr_client, &len);
//thread_parameter parameter;
//parameter.addr_client = &addr_client;
//parameter.fd = fd;
//parameter.len = len;
//CreateThread(NULL, NULL, pthread_tcp, ¶meter, NULL, NULL);
sock5_license(&addr_client, len, fd);
}
//释放DLL资源
WSACleanup();
}
// Client端
#include<iostream>
#include<winsock.h>
#pragma comment(lib,"ws2_32.lib")
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define BUFF_SIZE 1024 // 设置转发缓冲区
using namespace std;
//以下为协议结构体定义
//一、客户端认证请求
typedef struct client_license_request {
char ver; // 客户端的协议版本号 0x05:socks5 0x04:socks4
char nmethods; // 客户端所支持认证方式的长度
char methods[255]; //客户端支持的认证方式(可以有255种)
}client_license_request;
//二、服务端回应认证
typedef struct server_license_response {
char ver; // 服务端的协议版本号
char method; //服务端选择的认证方式
}server_license_response;
//三、客户端连接请求
#pragma pack(1)
typedef struct client_connect_request {
char ver; //客户端协议版本号
char cmd; //连接方式
char rsv = 0x00; //保留位0x00
char type; //类型
char addr[20] = "10.18.33.21"; //目的服务器ip
char port[6] = "2019"; //目的服务器端口
}client_connect_request;
//四、服务端回应连接
typedef struct server_connect_response {
char ver; //版本
char rep; //连接状态
char rsv; //保留0x00
char type; //类型
char addr[4]; //bind ip
char port[2]; //bind port
}server_connect_response;
// 一、初始化套接字库(WSAStartup)
void initialization()
{
// 初始化套接字库
WORD w_req = MAKEWORD(2, 2);// 版本号
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata); // WSAStartup 函数启动进程对 Winsock DLL的使用
if (err != 0)
{
cout << "初始化套接字库失败!" << endl;
}
else
{
cout << "初始化套接字库成功!" << endl;
}
//检测版本号
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
cout << "套接字库版本号不符!" << endl;
WSACleanup();
}
else {
cout << "套接字库版本正确!" << endl;
}
}
int main()
{
// 定义长度变量
int send_len = 0;
int recv_len = 0;
// 定义服务端套接字
SOCKET s_server;
// 服务端地址
SOCKADDR_IN server_addr;
// 一、初始化套接字库(WSAStartup)
initialization();
// -----------------------------------------------------------------------------------------
// 二、填充服务端协议地址信息
server_addr.sin_family = AF_INET; // 设置服务器地址家族
server_addr.sin_addr.S_un.S_addr = inet_addr("192.168.37.129"); // 主机 IPv4 地址,要跟哪个Sock5代理服务器通信
server_addr.sin_port = htons(2018); // 设置Sock5代理服务器端口号(与Sock5代理服务器一致)
// -----------------------------------------------------------------------------------------
// 三、创建套接字、 connect:客户端请求服务端连接
s_server = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字(流套接字)
if (connect(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) // connect客户端请求服务端连接
{
cout << "服务器连接失败!" << endl;
WSACleanup();
}
else
{
cout << "服务器连接成功!" << endl;
}
// -----------------------------------------------------------------------------------------
// 四、发送,接收数据(send/recv)
// 发送认证信息
client_license_request license_request;
license_request = {0};
license_request.ver = 0x5;
send_len = send(s_server, (char *)&license_request, sizeof(license_request),0);
if (send_len < 0)
{
cout << "验证认证信息失败!" << endl;
}
// 接收代理服务器的回应
server_license_response license_response;
license_response = { 0 };
recv(s_server,(char*)&license_response, sizeof(license_response), 0);
if (license_response.ver != 0x5 || license_response.method != 0x0)
{
cout << "代理服务器回应认证失败!" << endl;
}
// 向代理服务器发送请求
client_connect_request connect_request;
connect_request.ver = 0x5;
connect_request.cmd = 0x1;
connect_request.type = 0x01;
send_len = send(s_server,(char *)&connect_request, sizeof(client_connect_request) , 0);
if (send_len < 0)
{
cout << "向代理服务器发送请求失败!" << endl;
}
// 接收代理服务器是否成功
server_connect_response connect_response;
connect_response = { 0 };
recv_len = recv(s_server, (char*)&connect_response, sizeof(connect_response), 0);
if (recv_len < 0)
{
cout << "代理服务器接收请求失败!" << endl;
}
else if(connect_response.ver != 0x5 || connect_response.rep != 0x00 || connect_response.rsv != 0x00 || connect_response.type != 0x01)
{
cout << "代理服务器接收请求失败!" << endl;
}
else
{
cout << "向代理服务器发送请求成功!" << endl;
}
// -----------------------------------------------------------------------------------------
char recv_buffer[BUFF_SIZE];
char tmp[] = "Hello";
memcpy(recv_buffer, tmp, sizeof(tmp)+1);
send_len = send(s_server, (char *)&recv_buffer, sizeof(recv_buffer), 0);
char buff3[100];
recv_len = recv(s_server, (char*)&buff3, sizeof(buff3), 0);
printf(buff3);
exit:
//五、关闭套接字
closesocket(s_server);
//六、释放DLL资源
WSACleanup();
return 0;
}