网络传输之socket5代理服务

使用方法:

  1. 编译环境 win10 x64下 vs2019编译器
  2. server端如果启动失败使用管理员权限,默认监听1080端口。多线程(多客户端)情况下没有测试过,目前没有支持账号密码的形式访问。
  3. 客户端需要指定代理服务器的IP和端口和 目标服务器的IP和端口。
  4. 支持windows 自带的curl测试。

Server.cpp

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <cstring>
#include <algorithm>
#include <thread>

#pragma comment(lib, "ws2_32.lib")

#define SOCKS5_VERSION 0x05
#define AUTH_METHOD_NO_AUTH 0x00
#define REQUEST_CONNECT 0x01
#define ADDRESS_TYPE_IPV4 0x01
#define ADDRESS_TYPE_DOMAINNAME 0x03
#define ADDRESS_TYPE_IPV6 0x04

// SOCKS5 handshake request structure
struct Socks5HandshakeRequest {
    unsigned char version;        // SOCKS5 version number
    unsigned char nMethods;       // Number of supported authentication methods
    unsigned char authMethod[255];     // Chosen authentication method (0x00 - no authentication)

    // Constructor
    Socks5HandshakeRequest()
        : version(SOCKS5_VERSION), nMethods(0x01) 
    {
        memset(authMethod, 0, 255);
    }
};

// SOCKS5 handshake response structure
struct Socks5HandshakeResponse {
    unsigned char version;        // SOCKS5 version number
    unsigned char authMethod;     // Chosen authentication method (0x00 - no authentication)

    // Constructor
    Socks5HandshakeResponse(unsigned char v = SOCKS5_VERSION, unsigned char method = AUTH_METHOD_NO_AUTH)
        : version(v), authMethod(method) {}
};

// SOCKS5 connection request structure
#pragma pack(push, 1)  // Set structure to 1-byte alignment
struct Socks5ConnectRequest {
    unsigned char version;        // SOCKS5 version number
    unsigned char requestType;    // Request type (0x01 - CONNECT)
    unsigned char reserved;       // Reserved field
    unsigned char addressType;    // Address type (e.g., IPv4, domain name)
    unsigned short targetPort;    // Target port
    char domainNameLength;                   // Target host len 
    char* targetHost;       // Target host (domain name)


    // Constructor
    Socks5ConnectRequest(const char* host, unsigned short port)
        : version(SOCKS5_VERSION), requestType(REQUEST_CONNECT), reserved(0x00), addressType(ADDRESS_TYPE_DOMAINNAME),
        targetHost((char*) host), targetPort(port) {

    }

    // Convert the structure to a byte array for sending
    void toByteArray(unsigned char* buffer) const {
        buffer[0] = version;
        buffer[1] = requestType;
        buffer[2] = reserved;
        buffer[3] = addressType;
        memcpy(buffer+4, targetHost, domainNameLength);
        unsigned short port = htons(targetPort);
        buffer[domainNameLength + 6] = port & 0xFF;
        buffer[domainNameLength + 5] = (port >> 8) & 0xFF;

    }
};
#pragma pack(pop)  // Restore previous alignment

// SOCKS5 connection response structure
struct Socks5ConnectResponse {
    unsigned char version;        // SOCKS5 version number
    unsigned char reply;          // Response code (0x00 - success)
    unsigned char reserved;       // Reserved field
    unsigned char addressType;    // Address type
    unsigned char boundAddress[4]; // Bound address (IPv4)
    unsigned short boundPort;     // Bound port

    // Constructor
    Socks5ConnectResponse()
        : version(SOCKS5_VERSION), reply(0x00), reserved(0x00), addressType(ADDRESS_TYPE_IPV4) {
        std::memset(boundAddress, 0, sizeof(boundAddress));
        boundPort = 0;
    }
};

// Initialize Winsock
void initWinsock() {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup failed!" << std::endl;
        exit(1);
    }
}

// Handle client connection
void handleClient(SOCKET clientSocket) {
    // 1. Handshake phase
    Socks5HandshakeRequest handshakeRequest;
    int bytesReceived = recv(clientSocket, (char*)&handshakeRequest,sizeof(handshakeRequest), 0);

    if (handshakeRequest.version != SOCKS5_VERSION) {
        std::cerr << "Invalid SOCKS5 handshake request!" << std::endl;
        closesocket(clientSocket);
        return;
    }
    /*handshakeRequest.authMethod = new unsigned char[handshakeRequest.nMethods+1]{ };
    bytesReceived = recv(clientSocket, (char*)handshakeRequest.authMethod, handshakeRequest.nMethods, 0);
    if (bytesReceived != handshakeRequest.nMethods) {
        std::cerr << "Invalid SOCKS5 handshake request!" << std::endl;
        closesocket(clientSocket);
        return;
    }*/

    Socks5HandshakeResponse handshakeResponse;
    bytesReceived=send(clientSocket, (char*)&handshakeResponse, 2, 0);



    char connectbuffer[1024]{};

    // 2. Connection request phase
    Socks5ConnectRequest connectRequest("", 0);

    bytesReceived = recv(clientSocket, connectbuffer, 1024, 0);
    if (bytesReceived < 4) {
        std::cerr << "Invalid SOCKS5 connect request!" << std::endl;
        closesocket(clientSocket);
        return;
    }

    connectRequest.version = connectbuffer[0];
    connectRequest.requestType= connectbuffer[1];
    connectRequest.reserved=connectbuffer[2];
    connectRequest.addressType= connectbuffer[3];

    // Check address type // Target address and port

    char urlbuffer[256]{};
    if (connectRequest.addressType == ADDRESS_TYPE_IPV4) {
        connectRequest.targetHost = new char[4] {};
        connectRequest.domainNameLength = 4;
        memcpy(connectRequest.targetHost, &connectbuffer[4], 4);
        sprintf_s(urlbuffer, "%u.%u.%u.%u", (unsigned char)connectRequest.targetHost[0], (unsigned char)connectRequest.targetHost[1], (unsigned char)connectRequest.targetHost[2], (unsigned char)connectRequest.targetHost[3]);
        connectRequest.targetPort = connectbuffer[connectRequest.domainNameLength + 4];
        connectRequest.targetPort <<= 8;
        connectRequest.targetPort |= (unsigned char)connectbuffer[connectRequest.domainNameLength + 5];
    }
    else if (connectRequest.addressType == ADDRESS_TYPE_DOMAINNAME) {

        connectRequest.domainNameLength = connectbuffer[4];

        connectRequest.targetHost = new char[connectRequest.domainNameLength] {};

        struct hostent* host = gethostbyname(&connectbuffer[5]);
        if (host == NULL)
            return;
        for (int i = 0; host->h_addr_list[i]; i++)
        {
            strcpy_s(urlbuffer, inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
            break;
        }
        memcpy(connectRequest.targetHost, urlbuffer,strlen(urlbuffer));
        connectRequest.targetPort = connectbuffer[connectRequest.domainNameLength + 5];
        connectRequest.targetPort <<= 8;
        connectRequest.targetPort |= (unsigned char)connectbuffer[connectRequest.domainNameLength + 6];
    }
    else
    {
        std::cerr << "Only DOMAINNAME address type is supported!" << std::endl;
        closesocket(clientSocket);
        return;
    }

    


    std::cout << "Connecting to target: " << urlbuffer << ":" << connectRequest.targetPort << std::endl;

    // 3. Establish connection to target server
    SOCKET targetSocket = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in targetAddr;
    targetAddr.sin_family = AF_INET;
    targetAddr.sin_port = htons(connectRequest.targetPort);
    targetAddr.sin_addr.s_addr = inet_addr(urlbuffer);

    if (connect(targetSocket, (sockaddr*)&targetAddr, sizeof(targetAddr)) == SOCKET_ERROR) {
        std::cerr << "Failed to connect to target server!" << std::endl;
        // 4. Send connection failure response to client
        Socks5ConnectResponse connectResponse;
        connectResponse.reply = 1;
        send(clientSocket, (char*)&connectResponse, sizeof(connectResponse), 0);
        closesocket(clientSocket);
        return;
    }

    // 4. Send connection success response to client
    Socks5ConnectResponse connectResponse;
    send(clientSocket, (char*)&connectResponse, sizeof(connectResponse), 0);

    // 5. Data forwarding phase
    fd_set readfds;
    while (true) {
        FD_ZERO(&readfds);
        FD_SET(clientSocket, &readfds);
        FD_SET(targetSocket, &readfds);

        int selectResult = select(0, &readfds, nullptr, nullptr, nullptr);
        if (selectResult == SOCKET_ERROR) {
            std::cerr << "Select error!" << std::endl;
            break;
        }

        if (FD_ISSET(clientSocket, &readfds)) {
            char buffer[4096]{};
            int bytesReceived = recv(clientSocket, buffer, sizeof(buffer), 0);
            if (bytesReceived <= 0) {
                break; // Client closed connection
            }
            std::cout <<"clientSocket buffer:" << buffer << std::endl;
            send(targetSocket, buffer, bytesReceived, 0);
        }

        if (FD_ISSET(targetSocket, &readfds)) {
            char buffer[4096]{};
            int bytesReceived = recv(targetSocket, buffer, sizeof(buffer), 0);
            if (bytesReceived <= 0) {
                break; // Target server closed connection
            }
            std::cout << "targetSocket buffer:" << buffer << std::endl;
            send(clientSocket, buffer, bytesReceived, 0);
        }
    }

    // Clean up
    closesocket(clientSocket);
    closesocket(targetSocket);
}

// Start SOCKS5 server
SOCKET startSocks5Server(const std::string& serverHost, unsigned short serverPort) {
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == INVALID_SOCKET) {
        std::cerr << "Server socket creation failed!" << std::endl;
        exit(1);
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(serverPort);
    serverAddr.sin_addr.s_addr = INADDR_ANY;

    if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        std::cerr << "Binding failed!" << std::endl;
        closesocket(serverSocket);
        exit(1);
    }

    if (listen(serverSocket, 5) == SOCKET_ERROR) {
        std::cerr << "Listen failed!" << std::endl;
        closesocket(serverSocket);
        exit(1);
    }

    std::cout << "SOCKS5 server started on " << serverHost << ":" << serverPort << std::endl;
    return serverSocket;
}

int main() {
    // Initialize Winsock
    initWinsock();

    // Start SOCKS5 server
    SOCKET serverSocket = startSocks5Server("", 1080);

    // Accept client connections
    while (true) {
        SOCKET clientSocket = accept(serverSocket, nullptr, nullptr);
        if (clientSocket == INVALID_SOCKET) {
            std::cerr << "Accept failed!" << std::endl;
            continue;
        }

        // Create a threaded client connection
        std::thread(handleClient, clientSocket).detach();
    }

    // Close the server socket
    closesocket(serverSocket);
    WSACleanup();
    return 0;
}



client.cpp

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <cstring>
#include <algorithm>


#pragma comment(lib, "ws2_32.lib")

// SOCKS5 protocol constant definitions
#define SOCKS5_VERSION 0x05
#define AUTH_METHOD_NO_AUTH 0x00
#define REQUEST_CONNECT 0x01
#define ADDRESS_TYPE_IPV4 0x01
#define ADDRESS_TYPE_DOMAINNAME 0x03
#define ADDRESS_TYPE_IPV6 0x04

// SOCKS5 handshake request structure
struct Socks5HandshakeRequest {
    unsigned char version;        // SOCKS5 version number
    unsigned char nMethods;       // Number of supported authentication methods
    unsigned char authMethod[255];     // Chosen authentication method (0x00 - no authentication)

    // Constructor
    Socks5HandshakeRequest()
        : version(SOCKS5_VERSION), nMethods(0x01) {
        authMethod[0] = AUTH_METHOD_NO_AUTH;
    }
};

// SOCKS5 handshake response structure
struct Socks5HandshakeResponse {
    unsigned char version;        // SOCKS5 version number
    unsigned char authMethod;     // Chosen authentication method (0x00 - no authentication)

    // Constructor
    Socks5HandshakeResponse(unsigned char v = SOCKS5_VERSION, unsigned char method = AUTH_METHOD_NO_AUTH)
        : version(v), authMethod(method) {}
};


// SOCKS5 connection request structure
#pragma pack(push, 1)  // Set structure to 1-byte alignment
struct Socks5ConnectRequest {
    unsigned char version;        // SOCKS5 version number
    unsigned char requestType;    // Request type (0x01 - CONNECT)
    unsigned char reserved;       // Reserved field
    unsigned char addressType;    // Address type (e.g., IPv4, domain name)
    unsigned short targetPort;    // Target port
    char domainNameLength;                   // Target host len 
    char* targetHost;       // Target host (domain name)


    // Constructor
    Socks5ConnectRequest(const char* host, unsigned short port)
        : version(SOCKS5_VERSION), requestType(REQUEST_CONNECT), reserved(0x00), addressType(ADDRESS_TYPE_DOMAINNAME),
        targetHost((char*)host), targetPort(port) {

    }

    // Convert the structure to a byte array for sending
    int toByteArray(unsigned char* buffer) const {
        buffer[0] = version;
        buffer[1] = requestType;
        buffer[2] = reserved;
        buffer[3] = addressType;
        if (addressType == ADDRESS_TYPE_DOMAINNAME)
        {
            buffer[4] = domainNameLength;
            memcpy(buffer + 5, targetHost, domainNameLength);
            unsigned short port = htons(targetPort);
            buffer[domainNameLength + 5] = port & 0xFF;
            buffer[domainNameLength + 6] = (port >> 8) & 0xFF;
            return domainNameLength + 7;
        }
        else
        {
            memcpy(buffer + 4, targetHost, domainNameLength);
            unsigned short port = htons(targetPort);
            buffer[domainNameLength + 4] = port & 0xFF;
            buffer[domainNameLength + 5] = (port >> 8) & 0xFF;
            
        }
       
        return domainNameLength + 6;
    }
};
#pragma pack(pop)  // Restore previous alignment

// SOCKS5 connection response structure
struct Socks5ConnectResponse {
    unsigned char version;        // SOCKS5 version number
    unsigned char reply;          // Response code (0x00 - success)
    unsigned char reserved;       // Reserved field
    unsigned char addressType;    // Address type
    unsigned char boundAddress[4]; // Bound address (IPv4)
    unsigned short boundPort;     // Bound port

    // Constructor
    Socks5ConnectResponse()
        : version(SOCKS5_VERSION), reply(0x00), reserved(0x00), addressType(ADDRESS_TYPE_IPV4) {
        std::memset(boundAddress, 0, sizeof(boundAddress));
        boundPort = 0;
    }
};

// Target server connection information structure
struct TargetConnectionInfo {
    std::string targetHost;    // Target host address (domain name or IP address)
    unsigned short targetPort; // Target port

    // Constructor
    TargetConnectionInfo(const std::string& host, unsigned short port)
        : targetHost(host), targetPort(port) {}
};

// SOCKS5 proxy information structure
struct Socks5ProxyInfo {
    std::string proxyHost;     // SOCKS5 proxy server address
    unsigned short proxyPort;  // SOCKS5 proxy server port

    // Constructor
    Socks5ProxyInfo(const std::string& host, unsigned short port)
        : proxyHost(host), proxyPort(port) {}
};

// Complete connection information structure
struct ConnectionInfo {
    Socks5ProxyInfo proxyInfo;  // SOCKS5 proxy information
    TargetConnectionInfo targetInfo; // Target server information
    bool useSocks5Proxy;        // Whether to use SOCKS5 proxy

    // Constructor
    ConnectionInfo(const std::string& proxyHost, unsigned short proxyPort,
        const std::string& targetHost, unsigned short targetPort, bool useProxy)
        : proxyInfo(proxyHost, proxyPort), targetInfo(targetHost, targetPort), useSocks5Proxy(useProxy) {}
};

// HTTP request structure
struct HttpRequest {
    std::string method;        // Request method (e.g., GET, POST)
    std::string url;           // Request URL
    std::string host;          // Target host
    std::string connection;    // Connection options (e.g., close)

    // Constructor
    HttpRequest(const std::string& method, const std::string& url, const std::string& host)
        : method(method), url(url), host(host), connection("close") {}

    // Convert HTTP request to string
    std::string toString() const {
        std::stringstream ss;
        ss << method << " " << url << " HTTP/1.1\r\n";
        ss << "Host: " << host << "\r\n";
        ss << "Connection: " << connection << "\r\n";
        ss << "\r\n";
        return ss.str();
    }
};

// HTTP response structure
struct HttpResponse {
    int statusCode;           // Status code
    std::string statusMessage; // Status message
    std::string body;          // Response body

    // Constructor
    HttpResponse(int code, const std::string& message, const std::string& bodyContent)
        : statusCode(code), statusMessage(message), body(bodyContent) {}
};

// Initialize Winsock
void initWinsock() {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup failed!" << std::endl;
        exit(1);
    }
}

// Create and connect to the SOCKS5 proxy server
SOCKET connectToSocks5Proxy(const ConnectionInfo& connInfo) {
    SOCKET proxySocket = socket(AF_INET, SOCK_STREAM, 0);
    if (proxySocket == INVALID_SOCKET) {
        std::cerr << "Socket creation failed!" << std::endl;
        exit(1);
    }

    sockaddr_in proxyAddr;
    proxyAddr.sin_family = AF_INET;
    proxyAddr.sin_port = htons(connInfo.proxyInfo.proxyPort);
    proxyAddr.sin_addr.s_addr = inet_addr(connInfo.proxyInfo.proxyHost.c_str());

    if (connect(proxySocket, (sockaddr*)&proxyAddr, sizeof(proxyAddr)) == SOCKET_ERROR) {
        std::cerr << "Failed to connect to SOCKS5 proxy!" << std::endl;
        closesocket(proxySocket);
        exit(1);
    }

    return proxySocket;
}

// Send SOCKS5 handshake request and receive response
void socks5Handshake(SOCKET proxySocket) {
    Socks5HandshakeRequest handshakeRequest;
    send(proxySocket, (char*)&handshakeRequest, sizeof(handshakeRequest), 0);

    Socks5HandshakeResponse handshakeResponse;
    int bytesReceived = recv(proxySocket, (char*)&handshakeResponse, sizeof(handshakeResponse), 0);
    if (bytesReceived != 2 || handshakeResponse.version != SOCKS5_VERSION || handshakeResponse.authMethod != AUTH_METHOD_NO_AUTH) {
        std::cerr << "SOCKS5 handshake failed!" << std::endl;
        closesocket(proxySocket);
        exit(1);
    }
}

int GetIpString(std::string strIpAdd, char iIp[])
{
    int OppPos = 0, nowPos = 0, i = 0;
    while (OppPos = strIpAdd.find_first_of('.', nowPos))
    {
        if (std::string::npos == OppPos)
        {
            iIp[i] = atoi(strIpAdd.substr(nowPos).c_str());
            break;
        }
        iIp[i] = atoi(strIpAdd.substr(nowPos, OppPos - nowPos).c_str());
        nowPos = OppPos + 1;
        i++;
    }
    return i+1;
}

// Send connection request to target server
void sendConnectRequest(SOCKET proxySocket, const ConnectionInfo& connInfo) {
    
    char ip[125]{};
    int len=GetIpString(connInfo.targetInfo.targetHost.c_str(), ip);
    
    Socks5ConnectRequest connectRequest(connInfo.targetInfo.targetHost.c_str(), connInfo.targetInfo.targetPort);
    connectRequest.domainNameLength= strlen(connInfo.targetInfo.targetHost.c_str())+1;
    len = 7 + connectRequest.domainNameLength;
    unsigned char* requestBuffer = new unsigned char[len] {};
    len=connectRequest.toByteArray(requestBuffer);

    send(proxySocket, (char*)requestBuffer, len, 0);

    Socks5ConnectResponse connectResponse;
    int bytesReceived = recv(proxySocket, (char*)&connectResponse, sizeof(connectResponse), 0);
    if (bytesReceived != sizeof(connectResponse) || connectResponse.reply != 0x00) {
        std::cerr << "SOCKS5 connection request failed!" << std::endl;
        closesocket(proxySocket);
        exit(1);
    }
}

// Send HTTP request and receive response
void sendHttpRequest(SOCKET proxySocket, const ConnectionInfo& connInfo) {
    HttpRequest httpRequest("GET", "/", connInfo.targetInfo.targetHost);
    std::string requestStr = httpRequest.toString();

    // Send HTTP request
    send(proxySocket, requestStr.c_str(), requestStr.size(), 0);

    // Receive HTTP response
    char buffer[4096];
    int bytesReceived;
    while ((bytesReceived = recv(proxySocket, buffer, sizeof(buffer), 0)) > 0) {
        std::cout.write(buffer, bytesReceived);
    }
}

// Main function
int main() {
    // Create a ConnectionInfo structure instance
    ConnectionInfo connInfo(
        "127.0.0.1",   // SOCKS5 proxy server address
        1080,          // SOCKS5 proxy server port
        "127.0.0.1",  // Target host address
        888,            // Target port
        true            // Whether to use SOCKS5 proxy
    );

    // Initialize Winsock
    initWinsock();

    // Connect to SOCKS5 proxy
    SOCKET proxySocket = connectToSocks5Proxy(connInfo);

    // Perform SOCKS5 handshake
    socks5Handshake(proxySocket);

    // Send connection request to target server
    sendConnectRequest(proxySocket, connInfo);

    // Send HTTP request and receive response
    sendHttpRequest(proxySocket, connInfo);

    // Close connection
    closesocket(proxySocket);
    WSACleanup();
    return 0;
}