• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Пишем backconnect socks5 на C++ с нуля (Win/Linux/MacOS)

keklick1337

RAID-массив
Пользователь
Регистрация
06.05.2019
Сообщения
80
Реакции
183
Дорогие пользователи xss.pro, пишу данную статью для вас, чтобы ввести вас немного в курс дела, как можно достаточно просто написать свой backconnect socks5 сервер и клиент.
Это подробный пошаговый гайд, как "с нуля" написать backconnect SOCKS5.
Рассмотрим сервер и клиент по отдельности: начнём с пустого файла, постепенно дополним его нужными методами. Покажу логику и смысл каждой ключевой функции. Затем объединим всё в работающее решение.



Часть 1. Сервер (server_socks5.cpp)

1. Создаём скелет программы

Начинаем с самого простого каркаса main(), который принимает аргументы:

C++:
#include <iostream>
#include <string>

int main(int argc, char* argv[]) {
    // 1) Считать аргументы командной строки (порты, ключ и т.п.)
    // 2) Инициализация сетевых функций (Windows: WSAStartup)
    // 3) Запуск потоков: один для control-порта, один для SOCKS5
    // 4) Ожидать их завершения

    return 0;
}

Разбор
  • int main(int argc, char* argv[]) - обычная точка входа.
  • Мы планируем принимать:
    • -c <control_port> - порт для "управляющих" подключений от клиента (что сидит за NAT).
    • -S <socks_port> - порт, на котором будем принимать SOCKS5-запросы.
    • -x <xor_key> - ключ для XOR (для простенького шифрования).
    • -u <user> -p <pass> - логин/пароль (необязательно).
    • -d - отладочный режим (вывод детальной информации).

2. Подключаем заголовки для сетей и определяем платформозависимые вещи

Чтобы всё работало и на Windows, и на Linux/macOS, нужно аккуратно подключить разные заголовки.
Создадим блок:

C++:
#ifdef _WIN32
  #include <winsock2.h>
  #include <ws2tcpip.h>
  #pragma comment(lib, "ws2_32.lib")
  typedef SOCKET SocketType;
  #define CLOSESOCK closesocket
  #define SOCKERROR WSAGetLastError()
#else
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <arpa/inet.h>
  #include <unistd.h>
  #include <netdb.h>
  typedef int SocketType;
  #define CLOSESOCK close
  #define INVALID_SOCKET -1
  #define SOCKERROR errno
#endif

Разбор
  • #ifdef _WIN32 - компиляция под Windows. Используем WinSock2 (winsock2.h) и т.д.
  • Имена типов и функций в Windows отличаются, поэтому вводим алиасы:
    • typedef SOCKET SocketType;
    • #define CLOSESOCK closesocket
  • В Unix-системах всё проще (сокеты - это целые числа).

3. Функции инициализации сетей

Подготовим функции, которые будут удобными для старта/завершения:

C++:
bool initSockets() {
#ifdef _WIN32
    WSADATA wd;
    int res = WSAStartup(MAKEWORD(2,2), &wd);
    if(res != 0){
        std::cerr << "[Server] WSAStartup error=" << res << "\n";
        return false;
    }
#endif
    return true;
}

void cleanupSockets() {
#ifdef _WIN32
    WSACleanup();
#endif
}

Разбор
  • В Windows надо один раз вызвать WSAStartup(...). В Linux/macOS - ничего не нужно.

4. Создаём слушающий сокет

Нам нужно открыть TCP-сокет, привязать к порту, вызвать listen(...). Сделаем универсальную функцию:

C++:
SocketType createListeningSocket(uint16_t port){
    SocketType sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock == INVALID_SOCKET){
        std::cerr << "[Server] socket() error=" << SOCKERROR << "\n";
        return INVALID_SOCKET;
    }

    // Разрешим переиспользовать адрес
    int opt = 1;
#ifdef _WIN32
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
#else
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif

    sockaddr_in addr;
    std::memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0

    // bind
    if(bind(sock, (sockaddr*)&addr, sizeof(addr)) < 0){
        std::cerr << "[Server] bind error=" << SOCKERROR << "\n";
        CLOSESOCK(sock);
        return INVALID_SOCKET;
    }

    // listen
    if(listen(sock, 10) < 0){
        std::cerr << "[Server] listen error=" << SOCKERROR << "\n";
        CLOSESOCK(sock);
        return INVALID_SOCKET;
    }
    return sock;
}
Разбор
  • socket(AF_INET, SOCK_STREAM, 0) создаёт TCP-сокет (Stream).
  • bind(...) связывает сокет с нужным портом.
  • listen(...) переводит сокет в режим "принимать входящие".

5. XOR-функция

Нужно шифровать/дешифровать любой буфер. С помощью XOR всё просто:

C++:
void xorData(char* data, int len, const std::string &key){
    if(key.empty()) return; // если ключ пустой - ничего не делаем
    for(int i = 0; i < len; i++){
        data[i] ^= key[i % key.size()];
    }
}
Разбор
  • data ^= key[i % key.size()]- это и есть "XOR".
  • Один и тот же ключ применяем по кругу.

6. Отправка/приём с учётом "досылки" (sendAll, recvAll)

Часто send или recv возвращают меньше байт, чем запрашивали. Сделаем утилиты:

C++:
bool sendAll(SocketType s, const char* data, int len){
    int total = 0;
    while(total < len){
        int sent = send(s, data + total, len - total, 0);
        if(sent <= 0) return false;
        total += sent;
    }
    return true;
}

bool recvAll(SocketType s, char* buf, int len){
    int total = 0;
    while(total < len){
        int r = recv(s, buf + total, len - total, 0);
        if(r <= 0) return false;
        total += r;
    }
    return true;
}

Разбор
  • sendAll(...) в цикле шлёт, пока все байты не "утолкнёт" в сокет.
  • recvAll(...) аналогично ждёт, пока не получит все нужные байты (или вернётся с ошибкой).

7. sendEnc / recvEnc

Чтобы перед отправкой/приёмом выполнить XOR, сделаем, например, sendEnc (достаточно одной функции для отправки). Для приёма мы можем сразу применять XOR после получения. Ниже пример для отправки:

C++:
bool sendEnc(SocketType s, const char* data, int len, const std::string &key){
    if(s == INVALID_SOCKET) return false;
    std::vector<char> tmp(data, data + len);
    xorData(tmp.data(), len, key);   // "зашифровать" tmp
    return sendAll(s, tmp.data(), len);
}

Разбор
  • Копируем исходный буфер в tmp.
  • Делаем xorData(...).
  • Шлём зашифрованный результат.
Для приёма можно либо аналогично сделать recvEnc, либо вручную читать recvAll(...) и потом прогонять через xorData().

8. Реализация SOCKS5 (в режиме сервера)

8.1 Выбираем метод аутентификации
Когда к нашему SOCKS5-порту подключается клиент, он сначала отправляет:
1. Версию: 0x05
2. Количество поддерживаемых методов: N
3. Список методов.

Мы должны прочитать это, проверить, есть ли 0x00 (No Auth) или 0x02 (User/Pass). Затем ответить, какой метод выбрали.

C++:
bool socks5Handshake_SelectMethod(SocketType s, bool &useUserPass, const std::string &user, const std::string &pass) {
    unsigned char hdr[2];
    int r = recv(s, (char*)hdr, 2, 0);
    if(r < 2) return false;
    if(hdr[0] != 0x05) return false; // версия SOCKS5

    int nMethods = hdr[1];
    std::vector<unsigned char> methods(nMethods);
    r = recv(s, (char*)methods.data(), nMethods, 0);
    if(r < nMethods) return false;

    // Проверяем, нужно ли нам вообще auth (заданы ли user/pass)
    bool needAuth = (!user.empty() || !pass.empty());

    if(needAuth) {
        // Ищем METHOD_USERPASS (0x02)
        bool found = false;
        for(unsigned char m: methods){
            if(m == 0x02){
                found = true;
                break;
            }
        }
        if(!found) {
            // Отправляем REJECT (0xFF)
            unsigned char resp[2] = {0x05, 0xFF};
            sendAll(s, (char*)resp, 2);
            return false;
        }
        // Сигнализируем, что выбрали user/pass
        unsigned char resp[2] = {0x05, 0x02};
        sendAll(s, (char*)resp, 2);
        useUserPass = true;
    } else {
        // Без аутентификации
        bool found = false;
        for(unsigned char m: methods){
            if(m == 0x00){
                found = true;
                break;
            }
        }
        if(!found) {
            unsigned char resp[2] = {0x05, 0xFF};
            sendAll(s, (char*)resp, 2);
            return false;
        }
        unsigned char resp[2] = {0x05, 0x00};
        sendAll(s, (char*)resp, 2);
        useUserPass = false;
    }
    return true;
}

8.2 Аутентификация по user/pass
Если выбрано useUserPass, клиент отправит:
  • Версию subnegotiation (0x01).
  • Длину имени пользователя (1 байт).
  • Само имя пользователя.
  • Длину пароля (1 байт).
  • Сам пароль.
Мы проверим, совпадает ли с нашим user/pass.

C++:
bool socks5Handshake_UserPass(SocketType s, const std::string &user, const std::string &pass){
    unsigned char ver;
    if(recv(s, (char*)&ver, 1, 0) < 1) return false;
    if(ver != 0x01) return false; // subneg version = 1

    unsigned char ulen;
    if(recv(s, (char*)&ulen, 1, 0) < 1) return false;
    std::vector<char> uname(ulen);
    if(!recvAll(s, uname.data(), ulen)) return false;

    unsigned char plen;
    if(recv(s, (char*)&plen, 1, 0) < 1) return false;
    std::vector<char> upass(plen);
    if(!recvAll(s, upass.data(), plen)) return false;

    std::string su(uname.begin(), uname.end());
    std::string sp(upass.begin(), upass.end());

    // Сравним
    unsigned char status = 0x00;
    if(su != user || sp != pass){
        status = 0x01; // auth fail
    }

    unsigned char resp[2] = {0x01, status};
    sendAll(s, (char*)resp, 2);
    return (status == 0x00);
}
8.3 Обработка CONNECT
После выбора метода клиент посылает:
  • 1 байт: 0x05 (версия)
  • 1 байт: 0x01 (команда CONNECT)
  • 1 байт: 0x00 (зарезервировано)
  • 1 байт: тип адреса: 0x01 (IPv4) или 0x03 (домен)
Далее, если тип 0x01, идут 4 байта IPv4-адреса, потом 2 байта порта. Если 0x03, то 1 байт длины домена, далее сам домен, далее 2 байта порта.

C++:
bool socks5ParseConnect(SocketType s, uint32_t &ip, uint16_t &port) {
    unsigned char hdr[4];
    if(!recvAll(s, (char*)hdr, 4)) return false;
    // hdr[0] = 0x05 (версия), hdr[1] = 0x01 (CONNECT), hdr[2]=0x00, hdr[3]=ATYP
    if(hdr[0] != 0x05 || hdr[1] != 0x01 || hdr[2] != 0x00) return false;

    unsigned char atyp = hdr[3];
    if(atyp == 0x01){
        // IPv4
        unsigned char a4[4];
        if(!recvAll(s, (char*)a4, 4)) return false;
        // Собираем в 32-битное число (host order)
        ip = ( (uint32_t)a4[0] << 24 )
           | ( (uint32_t)a4[1] << 16 )
           | ( (uint32_t)a4[2] <<  8 )
           | ( (uint32_t)a4[3] );
        unsigned char pbuf[2];
        if(!recvAll(s, (char*)pbuf, 2)) return false;
        port = ((uint16_t)pbuf[0] << 8) | (uint16_t)pbuf[1];
    }
    else if(atyp == 0x03){
        // Доменное имя
        unsigned char dlen;
        if(!recvAll(s, (char*)&dlen, 1)) return false;
        std::vector<char> dom(dlen+1);
        if(!recvAll(s, dom.data(), dlen)) return false;
        dom[dlen] = '\0';

        unsigned char pbuf[2];
        if(!recvAll(s, (char*)pbuf, 2)) return false;
        port = ((uint16_t)pbuf[0] << 8) | (uint16_t)pbuf[1];

        // Резолвим домен
        struct hostent* he = gethostbyname(dom.data());
        if(!he) return false;
        struct in_addr[b] alist = (struct in_addr[/b])he->h_addr_list;
        if(!alist[0]) return false;
        ip = ntohl(alist[0]->s_addr);
    } else {
        return false;
    }
    return true;
}

8.4 Ответ CONNECT
Когда мы разобрались, нужно послать "ответ" от SOCKS5:
  • 0x05 (версия)
  • <rep> (код результата)
  • 0x00 (зарезервировано)
  • 0x01 (тип адреса = IPv4)
  • <4 байта IP>
  • <2 байта порт>
C++:
void socks5SendConnectReply(SocketType s, unsigned char rep, uint32_t ip=0, uint16_t port=0){
    unsigned char buf[10];
    buf[0] = 0x05;
    buf[1] = rep;    // 0x00 = успех, иначе ошибка
    buf[2] = 0x00;
    buf[3] = 0x01;   // IPv4
    uint32_t ip_n = htonl(ip);
    std::memcpy(buf + 4, &ip_n, 4);
    uint16_t p_n = htons(port);
    std::memcpy(buf + 8, &p_n, 2);
    sendAll(s, (char*)buf, 10);
}

9. Логика "backconnect" на сервере

На сервере есть глобальные переменные:

C++:
#include <mutex>
std::mutex g_mutex;
SocketType g_natClientSock = INVALID_SOCKET;  // сокет с NAT-клиентом
bool       g_natClientConnected = false;
std::string g_xorKey; // ключ XOR

9.1 Обработка SOCKS-подключения
В отдельном потоке (на каждое подключение к SOCKS-порту) делаем:

C++:
void handleSocksClient(SocketType sock,
                       const std::string &user,
                       const std::string &pass,
                       bool debug)
{
    bool useUserPass = false;
    // 1) Выбор метода (NoAuth или UserPass)
    if(!socks5Handshake_SelectMethod(sock, useUserPass, user, pass)){
        CLOSESOCK(sock);
        return;
    }

    // 2) Если выбрано user/pass, выполнить subneg
    if(useUserPass) {
        if(!socks5Handshake_UserPass(sock, user, pass)){
            CLOSESOCK(sock);
            return;
        }
    }

    // 3) Достаём IP, порт для CONNECT
    uint32_t tip = 0;
    uint16_t tport = 0;
    if(!socks5ParseConnect(sock, tip, tport)){
        socks5SendConnectReply(sock, 0x01); // ошибка
        CLOSESOCK(sock);
        return;
    }

    // 4) Создаем ephemeral socket (он примет соединение от NAT-клиента)
    SocketType epSock = socket(AF_INET, SOCK_STREAM, 0);
    if(epSock == INVALID_SOCKET){
        socks5SendConnectReply(sock, 0x01);
        CLOSESOCK(sock);
        return;
    }
    sockaddr_in ep;
    std::memset(&ep, 0, sizeof(ep));
    ep.sin_family = AF_INET;
    ep.sin_port   = 0; // сами выберем порт
    ep.sin_addr.s_addr = INADDR_ANY;
    if(bind(epSock, (sockaddr*)&ep, sizeof(ep)) < 0){
        socks5SendConnectReply(sock, 0x01);
        CLOSESOCK(epSock);
        CLOSESOCK(sock);
        return;
    }
    if(listen(epSock, 1) < 0){
        socks5SendConnectReply(sock, 0x01);
        CLOSESOCK(epSock);
        CLOSESOCK(sock);
        return;
    }
    sockaddr_in tmp;
    socklen_t sz = sizeof(tmp);
    getsockname(epSock, (sockaddr*)&tmp, &sz);
    uint16_t ephemeralPort = ntohs(tmp.sin_port);

    // 5) Отправляем команду 'C' нашему NAT-клиенту
    bool okSend = false;
    {
        std::lock_guard<std::mutex> lock(g_mutex);
        if(g_natClientSock != INVALID_SOCKET && g_natClientConnected){
            // Формируем пакет: 1 байт 'C' + 2 байта ephemeralPort + 4 байта IP + 2 байта targetPort
            char cmd[1 + 2 + 4 + 2];
            cmd[0] = 'C';
            uint16_t ep_n = htons(ephemeralPort);
            std::memcpy(cmd+1, &ep_n, 2);
            uint32_t tip_n = htonl(tip);
            std::memcpy(cmd+3, &tip_n, 4);
            uint16_t tpt_n = htons(tport);
            std::memcpy(cmd+7, &tpt_n, 2);

            okSend = sendEnc(g_natClientSock, cmd, sizeof(cmd), g_xorKey);
        }
    }
    if(!okSend){
        socks5SendConnectReply(sock, 0x05);
        CLOSESOCK(epSock);
        CLOSESOCK(sock);
        return;
    }

    // 6) Ждем подключение на epSock (нат-клиент туда зайдет)
    sockaddr_in from;
    socklen_t flen = sizeof(from);
    SocketType esock = accept(epSock, (sockaddr*)&from, &flen);
    CLOSESOCK(epSock); // уже не нужен
    if(esock == INVALID_SOCKET){
        socks5SendConnectReply(sock, 0x05);
        CLOSESOCK(sock);
        return;
    }

    // 7) Сообщаем SOCKS5-клиенту, что всё ок
    socks5SendConnectReply(sock, 0x00, tip, tport);

    // 8) Пересылаем данные в обоих направлениях
    std::thread tFwd([=](){
        char buf[4096];
        while(true){
            int rx = recv(sock, buf, 4096, 0);
            if(rx <= 0) break;
            int tx = send(esock, buf, rx, 0);
            if(tx <= 0) break;
        }
        CLOSESOCK(esock);
    });
    {
        char buf[4096];
        while(true){
            int rx = recv(esock, buf, 4096, 0);
            if(rx <= 0) break;
            int tx = send(sock, buf, rx, 0);
            if(tx <= 0) break;
        }
    }
    CLOSESOCK(esock);
    tFwd.join();
    CLOSESOCK(sock);
}

Разбор по шагам
  1. Считываем SOCKS5-запрос.
  2. Создаём временный порт (epSock).
  3. Шлём через NAT-клиенту команду C.
  4. Ждём, когда NAT-клиент "зайдёт" в этот epSock.
  5. После удачного соединения отвечаем SOCKS-клиенту 0x00 (OK).
  6. Гоним трафик туда-сюда.

10. Обработка control-порта

В отдельном потоке слушаем -c <control_port> и принимаем единственного NAT-клиента:

C++:
#include <chrono>

void controlAcceptLoop(uint16_t cPort, bool debug){
    SocketType listener = createListeningSocket(cPort);
    if(listener == INVALID_SOCKET){
        std::cerr << "[Server] Failed to listen on controlPort=" << cPort << "\n";
        return;
    }
    std::cout << "[Server] Waiting for NAT client on port " << cPort << "...\n";

    while(true){
        sockaddr_in caddr;
        socklen_t clen = sizeof(caddr);
        SocketType cs = accept(listener, (sockaddr*)&caddr, &clen);
        if(cs == INVALID_SOCKET){
            std::cerr << "[Server] accept() error on control channel\n";
            break;
        }

        // Проверим, не занят ли уже кто-то
        {
            std::lock_guard<std::mutex> lock(g_mutex);
            if(g_natClientConnected){
                // Уже подключен NAT-клиент
                const char msg[] = "OCCUP";
                // зашифруем
                sendEnc(cs, msg, 5, g_xorKey);
                CLOSESOCK(cs);
                continue;
            }
        }

        // Считываем 5 байт "HELLO" (XOR)
        char buf[5];
        bool okHello = false;
        // Хотим таймаут, например 5 секунд
#ifdef _WIN32
        DWORD tmo = 5000;
        setsockopt(cs, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tmo, sizeof(tmo));
#else
        struct timeval tv;
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        setsockopt(cs, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv));
#endif
        if(recvAll(cs, buf, 5)) {
            // XOR
            xorData(buf, 5, g_xorKey);
            if(std::string(buf, 5) == "HELLO"){
                okHello = true;
            }
        }

        if(!okHello){
            CLOSESOCK(cs);
            continue;
        }

        // Отправим "OK"
        const char msgOk[] = "OK";
        sendEnc(cs, msgOk, 2, g_xorKey);

        // Считаем, что клиент подключился
        {
            std::lock_guard<std::mutex> lock(g_mutex);
            g_natClientSock = cs;
            g_natClientConnected = true;
        }
        std::cout << "[Server] NAT client connected!\n";

        // Читаем keep-alive ('K') или EOF
        while(true){
            char c;
            int r = recv(cs, &c, 1, 0);
            if(r <= 0){
                // отключился
                CLOSESOCK(cs);
                std::lock_guard<std::mutex> lock(g_mutex);
                g_natClientSock = INVALID_SOCKET;
                g_natClientConnected = false;
                break;
            }
            c ^= g_xorKey[0];
            if(c == 'K'){
                // keep-alive
            } else {
                // неизвестно, игнорируем
            }
        }
    }
    CLOSESOCK(listener);
}

11. Запуск потока SOCKS5 и control

В main() прописываем логику:

C++:
#include <thread>
#include <cstdlib>

int main(int argc, char* argv[]){
    // 1) Парсим аргументы
    uint16_t controlPort = 0;
    uint16_t socksPort   = 0;
    std::string user, pass;
    bool debug = false;
    // ... (парсим -c, -S, -x, -u, -p, -d и т.д.)

    if(!initSockets()){
        return 1;
    }

    // Стартуем поток, слушающий контрольный порт
    std::thread tCtl(controlAcceptLoop, controlPort, debug);

    // Запустим SOCKS5-listener
    SocketType socksListener = createListeningSocket(socksPort);
    if(socksListener == INVALID_SOCKET){
        std::cerr << "[Server] Unable to listen on " << socksPort << "\n";
        return 1;
    }

    while(true){
        sockaddr_in saddr;
        socklen_t slen = sizeof(saddr);
        SocketType c = accept(socksListener, (sockaddr*)&saddr, &slen);
        if(c == INVALID_SOCKET){
            // ошибка или завершение
            break;
        }
        // Запустим поток handleSocksClient
        std::thread th(handleSocksClient, c, user, pass, debug);
        th.detach();
    }

    CLOSESOCK(socksListener);
    tCtl.join();
    cleanupSockets();
    return 0;
}
Таким образом, сервер готов.


Часть 2. Клиент (client_socks5.cpp)

Теперь клиент, который сидит за NAT. Он сам коннектится к серверу и держит соединение. Когда сервер просит "создать туннель", мы выполняем команду C.

1. Структура main()

C++:
int main(int argc, char* argv[]){
    // 1) Парсим -s <server_ip>, -c <control_port>, -x <xor_key>, -d (debug).
    // 2) initSockets()
    // 3) Запускаем цикл connect -> если ок, controlChannelLoop()
    // 4) Если отвалилось, повторяем

    return 0;
}

2. Цикл переподключения
Нам нужен вечный цикл:
C++:
void runClientLoop(const std::string &serverIP,
                   uint16_t controlPort,
                   const std::string &xorKey,
                   bool debug)
{
    while(true){
        // создаём сокет
        SocketType s = socket(AF_INET, SOCK_STREAM, 0);
        if(s == INVALID_SOCKET){
            if(debug) std::cerr << "socket() error\n";
#ifdef _WIN32
            Sleep(5000);
#else
            sleep(5);
#endif
            continue;
        }
        // Подключаемся к serverIP:controlPort
        sockaddr_in addr;
        std::memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_port   = htons(controlPort);
        inet_pton(AF_INET, serverIP.c_str(), &addr.sin_addr);

        if(connect(s, (sockaddr*)&addr, sizeof(addr)) < 0){
            if(debug) std::cerr << "Can't connect to server\n";
            CLOSESOCK(s);
#ifdef _WIN32
            Sleep(5000);
#else
            sleep(5);
#endif
            continue;
        }
        std::cout << "[Client] Connected to " << serverIP << ":" << controlPort << "\n";

        // Переходим в функцию, которая ведёт обмен по control-каналу
        controlChannelLoop(s, xorKey, debug);

        // Если вышли - значит соединение отвалилось
        CLOSESOCK(s);
        std::cerr << "[Client] Control connection closed. Retry in 5s...\n";
#ifdef _WIN32
        Sleep(5000);
#else
        sleep(5);
#endif
    }
}

3. controlChannelLoop(): отправляем "HELLO", ждём "OK"

C++:
void controlChannelLoop(SocketType ctrlSock,
                        const std::string &xorKey,
                        bool debug)
{
    // 1) Шлём "HELLO"
    {
        char hello[5] = {'H','E','L','L','O'};
        if(!sendEnc(ctrlSock, hello, 5, xorKey)){
            if(debug) std::cerr << "[Client] fail to send HELLO\n";
            return;
        }
    }
    // 2) Ждём ответ (5 байт, может быть "OK" или "OCCUP")
    char resp[5];
    int r = recv(ctrlSock, resp, 5, 0);
    if(r <= 0){
        if(debug) std::cerr << "[Client] no response\n";
        return;
    }
    // XOR
    for(int i=0; i<r; i++){
        resp[i] ^= xorKey[i % xorKey.size()];
    }
    std::string sresp(resp, r);
    if(sresp == "OCCUP"){
        std::cerr << "[Client] Server is busy.\n";
        return;
    } else if(sresp != "OK"){
        if(debug) std::cerr << "[Client] unknown handshake response\n";
        return;
    }
    std::cout << "[Client] XOR-handshake succeeded (OK)\n";

    // 3) Запускаем keepAlive
    std::thread ka(keepAliveThread, ctrlSock, xorKey, debug);

    // 4) Читаем команды: 'C' ...
    while(true){
        char cmd;
        int rc = recv(ctrlSock, &cmd, 1, 0);
        if(rc <= 0){
            // разрыв
            break;
        }
        // XOR
        cmd ^= xorKey[0];
        if(cmd == 'C'){
            // Читаем 8 байт:
            // ephemeralPort (2 байта), targetIP (4 байта), targetPort (2 байта)
            char buf[8];
            if(!recvAll(ctrlSock, buf, 8)) break;
            for(int i=0; i<8; i++){
                buf[i] ^= xorKey[(1 + i) % xorKey.size()];
            }
            // Разбираем
            uint16_t ep_n;
            std::memcpy(&ep_n, buf, 2);
            uint16_t ephemeralPort = ntohs(ep_n);

            uint32_t tip_n;
            std::memcpy(&tip_n, buf+2, 4);

            uint16_t tpt_n;
            std::memcpy(&tpt_n, buf+6, 2);
            uint16_t targetPort = ntohs(tpt_n);

            // Запустим отдельный поток, который сделает connectLocal() и connectServerEphemeral()
            std::thread th(handleCommandC, ephemeralPort, tip_n, targetPort, xorKey, debug);
            th.detach();
        } else {
            // неизвестно
        }
    }

    ka.join();
}

4. Keep-Alive-поток

C++:
void keepAliveThread(SocketType ctrlSock,
                     const std::string &xorKey,
                     bool debug)
{
    while(true){
#ifdef _WIN32
        Sleep(15000);
#else
        sleep(15);
#endif
        char c = 'K';
        c ^= xorKey[0];
        int rc = send(ctrlSock, &c, 1, 0);
        if(rc <= 0){
            if(debug) std::cerr << "[Client] keepAlive send fail\n";
            break;
        }
    }
}

5. Обработка команды C: открыть локальное соединение и "сшить" его с ephemeral

C++:
SocketType connectLocal(uint32_t ip_n, uint16_t port_h){
    // ip_n - в сетевом порядке
    // port_h - в host-порядке
    SocketType s = socket(AF_INET, SOCK_STREAM, 0);
    if(s == INVALID_SOCKET) return INVALID_SOCKET;
    sockaddr_in addr;
    std::memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = ip_n; // уже network order
    addr.sin_port = htons(port_h);

    if(connect(s, (sockaddr*)&addr, sizeof(addr)) < 0){
        CLOSESOCK(s);
        return INVALID_SOCKET;
    }
    return s;
}

SocketType connectServerEphemeral(const std::string &serverIP,
                                  uint16_t ephemeralPort)
{
    SocketType s = socket(AF_INET, SOCK_STREAM, 0);
    if(s == INVALID_SOCKET) return INVALID_SOCKET;
    sockaddr_in addr;
    std::memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(ephemeralPort);
    inet_pton(AF_INET, serverIP.c_str(), &addr.sin_addr);

    if(connect(s, (sockaddr*)&addr, sizeof(addr)) < 0){
        CLOSESOCK(s);
        return INVALID_SOCKET;
    }
    return s;
}

void handleCommandC(uint16_t ephemeralPort,
                    uint32_t targetIP_n,
                    uint16_t targetPort_h,
                    const std::string &xorKey,
                    bool debug)
{
    // Шаг 1: подключиться к ephemeral-порту на сервере
    SocketType esock = connectServerEphemeral(g_serverIP, ephemeralPort);
    if(esock == INVALID_SOCKET){
        if(debug) std::cerr << "[Client] can't connect ephemeral\n";
        return;
    }
    // Шаг 2: локальное соединение
    SocketType localSock = connectLocal(targetIP_n, targetPort_h);
    if(localSock == INVALID_SOCKET){
        if(debug) std::cerr << "[Client] can't connect local\n";
        CLOSESOCK(esock);
        return;
    }
    // Шаг 3: пересылка
    std::thread tFwd([=](){
        char b[4096];
        while(true){
            int rx = recv(esock, b, 4096, 0);
            if(rx <= 0) break;
            int tx = send(localSock, b, rx, 0);
            if(tx <= 0) break;
        }
        CLOSESOCK(localSock);
    });
    {
        char b[4096];
        while(true){
            int rx = recv(localSock, b, 4096, 0);
            if(rx <= 0) break;
            int tx = send(esock, b, rx, 0);
            if(tx <= 0) break;
        }
    }
    CLOSESOCK(esock);
    tFwd.join();
}

Разбор
  • connectLocal(...) подключается внутри домашней сети (куда попросил сервер).
  • connectServerEphemeral(...) идёт на сервер, который слушает ephemeralPort.
  • "Сшиваем" оба сокета, гоняя байты в обоих направлениях.

6. Итоговый main()

C++:
int main(int argc, char* argv[]){
    // 1) Парсим (примерно):
    // -s <server_ip>, -c <control_port>, -x <xor_key>, -d
    // 2) initSockets()
    // 3) runClientLoop(serverIP, controlPort, xorKey, debug)
    // 4) cleanupSockets()
    return 0;
}

Итог

Мы прошлись шаг за шагом, как "с нуля" написать backconnect SOCKS5:
  1. Сервер:
    1. Слушает control-порт.
    2. Слушает SOCKS5-порт, обрабатывает CONNECT-запросы.
    3. Когда приходит CONNECT, создаём ephemeral сокет, шлём команду C клиенту.
    4. Клиент возвращается на этот ephemeral, и мы пересылаем данные.
  2. Клиент:
    1. Постоянно переподключается к серверу (control-порт).
    2. При подключении шлёт "HELLO", ждёт "OK".
    3. Слушает команды C: открывает локальное соединение и цепляется к ephemeral порту на сервере.
    4. Гонит байты туда-сюда.
  3. XOR:
    1. Зашифровывает простым XOR все служебные данные ("HELLO", "OK", "C" и т.д.).

Такую схему можно усложнить или облегчить: добавить более надёжную криптографию, управлять списком доступных ресурсов, реализовать UDP и т.д. Но базовая идея уже есть.

Исходный код проекта из статьи опубликован тут https://github.com/keklick1337/backconnect_socks5

В ближайшем будущем (наверное) добавлю версию клиента на чистом WINAPI для сборки с /NODEFAULTLIB
---

Автор: Vladislav Tislenko aka keklick1337 (https://github.com/keklick1337)
Статья специально для xss.pro
 
Последнее редактирование:
Подробный разбор исходного кода (client_socks5.cpp / server_socks5.cpp)
Данный текст создан специально для новичков, максимально подробно, почти "построчно" объясняя, что происходит. Ниже идёт описание client_socks5.cpp и server_socks5.cpp.

Часть 1. client_socks5.cpp - клиентская часть, которая сидит за NAT

Общее назначение:
  • Программа подключается к серверу (по IP + control_port) и проходит XOR-хендшейк (отправляет "HELLO", получает "OK").
  • Поддерживает keepalive ("K" каждые 15 секунд), чтобы сервер знал, что клиент на связи.
  • Когда сервер даёт команду 'C', клиент:
    1. Подключается обратно к "вспомогательному" (ephemeral) порту на сервере.
    2. Подключается к целевому локальному ресурсу (у себя в LAN).
    3. Сшивает оба соединения (сервер ↔ клиент ↔ локальная цель), давая серверу "достучаться" до локалки за NAT.

1. Глобальные переменные и хедеры
  • Подключаются заголовки: для сокетов (Cross-platform), для string, vector, thread и т.д.
  • Объявляются: g_debug (режим отладки), g_xorKey (строка-ключ XOR), g_serverIP (адрес сервера), g_controlPort (порт для контролей).

2. initSockets / cleanupSockets
В Windows обязательно вызывать WSAStartup. В Unix/Linux/macOS это не нужно:
  • initSockets() - возвращает true, если всё ок.
  • cleanupSockets() - завершает (Windows: WSACleanup()).

3. XOR-функция xorData()
C++:
void xorData(char* data, int len, const std::string &key){
    if(key.empty()) return;
    for(int i=0; i<len; i++){
        data[i]^= key[i % key.size()];
    }
}
  • Берёт буфер data, длину len, "пробегается" по каждому байту и "XOR-ит" его с соответствующим байтом ключа (key).
  • Если ключ пустой - ничего не делается.

4. sendAll / recvAll
C++:
bool sendAll(SocketType s, const char* data, int len) {
   // ...
}
bool recvAll(SocketType s, char* buf, int len) {
   // ...
}
  • Утилиты, которые в цикле стараются "дослать" или "дочитать" ровно нужное количество байт, поскольку системные вызовы send и recv могут вернуть меньше.

5. sendEnc
C++:
bool sendEnc(SocketType s, const char* data, int len){
    std::vector<char> tmp(data,data+len);
    xorData(tmp.data(), len, g_xorKey);
    return sendAll(s, tmp.data(), len);
}
  • Сначала копирует исходные данные в tmp.
  • Прогоняет xorData, "зашифровывает".
  • Отправляет "зашифрованные" данные по сокету (через sendAll).
Таким образом, сервер (или клиент) на другом конце должен "раскодировать" их, тоже применив xorData.

6. connectLocal / connectServerEphemeral
C++:
SocketType connectLocal(uint32_t ip_n, uint16_t port_n)
SocketType connectServerEphemeral(const std::string &ip, uint16_t port)
  • Первая функция connectLocal - подключается к локальному IP:port внутри домашней сети. Здесь ip_n - уже в сетевом порядке байт, порт - в host-порядке (поэтому вызывается htons).
  • Вторая connectServerEphemeral - подключается к серверу на "временный" (ephemeral) порт, который сервер назначил.
  • Если connect не удаётся, возвращаем INVALID_SOCKET.

7. handleCommandC
C++:
void handleCommandC(uint16_t ephemeralPort, uint32_t targetIP_n, uint16_t targetPort_h)
  1. Получаем ephemeralPort (порт сервера, к которому надо подключиться).
  2. Получаем targetIP_n/targetPort_h - адрес внутри домашней сети (в network order/host order), куда клиент должен подключиться.
  3. Делаем connectServerEphemeral → esock.
  4. Делаем connectLocal → localSock.
  5. Если оба успешны - "сшиваем": в отдельном потоке пересылаем esock→localSock, а в текущем - localSock→esock.
  6. Когда одна сторона отвалилась, закрываем сокеты.

8. keepAliveThread
C++:
void keepAliveThread(SocketType ctrlSock){
    ...
    // каждые 15 секунд отправляет 'K'
}
  • Каждые 15 секунд отправляет символ 'K', за-XOR-енный первым байтом ключа.
  • Если отправка не удалась - считаем, что соединение закрылось.

9. controlChannelLoop
C++:
void controlChannelLoop(SocketType ctrl) {
   // 1) Отправляем "HELLO"
   // 2) Ждём ответ ("OK"/"OCCUP"), декодируем XOR
   // 3) Запускаем keepAliveThread
   // 4) В цикле читаем команды ('C'), парсим 8 байт параметров, запускаем handleCommandC
}
  • Это "сердце" клиента. При старте мы шлём "HELLO" (XOR), ожидаем "OK" или "OCCUP" от сервера.
  • Если "OCCUP" - значит, сервер "занят" (другой NAT-клиент уже подключён).
  • Если "OK" - значит всё ок, запускаем keepAliveThread.
  • Затем в бесконечном цикле ждём команду 'C'. Если пришла - читаем доп. 8 байт (порт, IP, порт), XOR-раскодируем, запускаем handleCommandC в новом потоке.
  • Если сервер разрывает соединение (recv <= 0) - завершаемся.

10. runClientLoop
C++:
void runClientLoop(){
   while(true){
       // 1) socket()
       // 2) connect(...) к g_serverIP:g_controlPort
       // 3) Если ок -> controlChannelLoop(s)
       // 4) Закрываем, спим 5с, повторяем
   }
}
  • Бесконечный цикл переподключения. Если связь рвётся - через 5 секунд пробуем снова.

11. main()
C++:
int main(int argc, char* argv[]){
   // 1) Парсим аргументы (-s, -c, -x, -d)
   // 2) initSockets()
   // 3) runClientLoop()
   // 4) cleanupSockets()
}
  • Считываем g_serverIP, g_controlPort, g_xorKey.
  • Запускаем runClientLoop, пока программа не завершится.

Таким образом, client_socks5.cpp сидит у пользователя за NAT, подключается к серверу, держит канал, обрабатывает команды "C" (т.е. "соединись локально и вернись на серверный ephemeral-порт"), что даёт backconnect-логику.


Часть 2. server_socks5.cpp - серверная часть

Общее назначение:
  • Слушает control_port, ждёт единственного NAT-клиента. Как только тот присоединился и сказал "HELLO" (XOR) - считаем, что g_natClientConnected = true.
  • Слушает SOCKS5-порт. Когда приходит обычный SOCKS5-клиент (из интернета), он может выполнить команду CONNECT.
  • При CONNECT, сервер создаёт ephemeral сокет, потом шлёт NAT-клиенту команду 'C'. Тот вернётся на ephemeral-порт, и мы "сшиваем" оба канала. Таким образом, пользователь "за NAT" предоставляет "туннель".

1. Глобальные переменные, мьютекс
C++:
std::mutex g_mutex;
SocketType g_natClientSock = INVALID_SOCKET;
bool       g_natClientConnected = false;
std::string g_xorKey;
...
  • Мьютекс (g_mutex) нужен, чтобы защищать доступ к g_natClientSock.
  • Если g_natClientConnected == true, значит клиент "обитает" на g_natClientSock.

2. initSockets / cleanupSockets, sendAll / recvAll, sendEnc
Все те же, что и в клиенте, но уже для сервера:
  • initSockets()/cleanupSockets() - инициализация/зачистка.
  • sendAll/recvAll - "досылки".
  • sendEnc - отправка с XOR.

3. createListeningSocket
C++:
SocketType createListeningSocket(uint16_t port){
   // socket -> bind -> listen
}
  • Создаём TCP-сокет, биндим на port, делаем listen.
  • Возвращаем дескриптор или INVALID_SOCKET при ошибке.

4. SOCKS5-handshake: socks5Handshake_SelectMethod, socks5Handshake_UserPass
  • socks5Handshake_SelectMethod:
    - Читает байты: 0x05 (версия), N (число методов), затем N методов.
    - Если g_socksUser/g_socksPass не пусты - значит, нужно USER/PASS (0x02). Иначе 0x00 (NOAUTH).
    - Возвращаем useUserPass = true/false.
  • socks5Handshake_UserPass:
    - Читает версию (0x01), длину логина, логин, длину пароля, пароль.
    - Сравнивает с g_socksUser/g_socksPass.
    - Возвращает true, если всё совпало.

5. socks5ParseConnect / socks5SendConnectReply
  • socks5ParseConnect читает: 0x05 (версия), 0x01 (команда CONNECT), 0x00, ATYP.
    - Если ATYP=0x01 (IPv4), то считываем 4 байта адреса и 2 байта порта.
    - Если ATYP=0x03 (домен), то 1 байт длины домена, далее сам домен, далее порт. Резолвим через gethostbyname.
    - Заполняем ip (host-порядок) и port.
  • socks5SendConnectReply отправляет 10 байт: 0x05, rep, 0x00, 0x01, 4 байта IP, 2 байта PORT.
    - rep=0x00, если успех; rep=0x01 - любая ошибка.

6. handleSocksClient
Самый главный метод для "большого" цикла SOCKS:
C++:
void handleSocksClient(SocketType sock){
   // 1) SOCKS5-handshake, optional user/pass
   // 2) parse CONNECT (получаем tip, tport)
   // 3) Создаём ephemeral socket epSock (bind=0, listen=1, узнаём ephemeralPort)
   // 4) Лочим mutex, шлём команду 'C' nat-клиенту (cmd[0]='C', ...)
   // 5) Ждём accept(epSock)
   // 6) Отправляем socks5SendConnectReply(sock, 0x00)
   // 7) Пересылка в отдельном потоке sock->esock и в текущем esock->sock
}
  • Если g_natClientConnected=false, или отправка команды "C" не удалась - отвечаем SOCKS5-клиенту ошибкой (rep=0x05).
  • Если всё ок, клиент за NAT вернётся в ephemeralPort, мы сделаем accept -> получим esock.
  • Отправим SOCKS5-клиенту 0x00 (успех), и начнём гонять трафик туда-сюда.

7. controlAcceptLoop
C++:
void controlAcceptLoop(uint16_t cPort){
   // 1) createListeningSocket(cPort)
   // 2) В цикле accept()
   // 3) Если g_natClientConnected=true, отправляем "OCCUP"
   // 4) Иначе ждем 5 байт "HELLO" (с таймаутом 5с)
   // 5) Если "HELLO" ок, шлем "OK"
   // 6) Сохраняем g_natClientSock=cs, g_natClientConnected=true
   // 7) Ждем 'K' (keepalive) или EOF
}
  • Таким образом, сервер ждёт одного клиента NAT. Когда тот отваливается - слушаем следующего.
  • Если уже кто-то сидит, отвечаем "OCCUP".
  • При успешном "HELLO" → "OK" (XOR), выставляем g_natClientConnected=true.

8. main()
C++:
int main(int argc, char* argv[]){
   // 1) Парсим -c <control_port>, -S <socks_port>, -x <xor_key>, ...
   // 2) initSockets()
   // 3) std::thread tCtl(controlAcceptLoop, controlPort)
   // 4) Создаём socksListener = createListeningSocket(socksPort)
   // 5) В цикле accept(), для каждого клиента -> std::thread(handleSocksClient, c).detach()
   // 6) tCtl.join()
}
  • Основная точка входа. Запускаем controlAcceptLoop (в отдельном потоке) и SOCKS5 accept (в основном).
  • Параметры user/pass (опционально) включают аутентификацию для SOCKS5.

Итого:
  1. Сервер ждёт NAT-клиента на control_port. При успешном "HELLO" → "OK" мы считаем клиент "подключён".
  2. Сервер также слушает SOCKS5-порт, принимает команды CONNECT.
  3. При CONNECT - создаём временный ephemeral порт, отправляем клиенту "C + параметры".
  4. Клиент, получив "C", подключается назад к ephemeral-порту, одновременно подключаясь к локалке (targetIP/targetPort).
  5. Таким образом получаем туннель SOCKS-клиент (интернет) ↔ сервер ↔ NAT-клиент ↔ локальный ресурс.
  6. Все управляющие данные "XOR-ятся" ключом g_xorKey. Это не настоящая криптография, просто маскировка.

Таким образом, данный пошаговый разбор позволяет даже "новичку" понять логику backconnect SOCKS5 на основе двух программ (клиент/сервер). Всё, что осталось - это скомпилировать, запустить сервер (с ключом, паролем/логином), затем запустить клиента (с тем же ключом) - и проверить работу "проброса" соединений через NAT в свою домашнюю сеть.
 
хорошо что апнул, всеравно собирался искать тему. есть вопрос по сборке.
почему vs2015 создаёт debug directory в бинарнике ? как отключить ? или качать vs2013 ?
Минимальная версия C++ для сборки - C++17
Можно сделать релизную сборку. Я для примера положил bat файлы под сборку.
Эта статья по сути пример, как можно делать.
Вскоре на чистом WinAPI опубликую клиент под Windows, тоже как пример и дополню третий пост
 
Мил человек, ты уж или крестик сними или трусы надень. Извини, может несколько грубо, но это говнокод обыкновенный. Нахрена тебе здесь с++ 17, если ты ничего из него не используешь?
У тебя тут даже не "Си с классами", а "Процедурное программирование на С++, с использованием отдельных фич языка".
Начнем с азов.
1) за разделение кроссплатформенного кода дефайнами надо стрелять на месте. Люди наверное зря придумали все эти ваши ООП, фабрики и прочую поебень. Как бы тут не глядя даже просится базовый абстрактный класс, его реализация через фабричный метод. Откройте любой учебник по паттернам - там это всё разжевано.
2) из STL у Вас только std::vector да и то как невнятная замена обычному массиву. xorData очень легко заменятся лямбдой, а про std::for_each. например, Вы тоже не слышали.
3) будь я Вашим тимлидом - этот код бы никогда не прошел ревью. Там еще много чего можно написать. Вы не подумайте, я ни в коем случае не хочу Вас ни коим образом обидеть, но если Вы хотите выжить в эпоху ИИ, то такой код недопустим. Учите матчасть.
 
Мил человек, ты уж или крестик сними или трусы надень. Извини, может несколько грубо, но это говнокод обыкновенный. Нахрена тебе здесь с++ 17, если ты ничего из него не используешь?
У тебя тут даже не "Си с классами", а "Процедурное программирование на С++, с использованием отдельных фич языка".
Начнем с азов.
1) за разделение кроссплатформенного кода дефайнами надо стрелять на месте. Люди наверное зря придумали все эти ваши ООП, фабрики и прочую поебень. Как бы тут не глядя даже просится базовый абстрактный класс, его реализация через фабричный метод. Откройте любой учебник по паттернам - там это всё разжевано.
2) из STL у Вас только std::vector да и то как невнятная замена обычному массиву. xorData очень легко заменятся лямбдой, а про std::for_each. например, Вы тоже не слышали.
3) будь я Вашим тимлидом - этот код бы никогда не прошел ревью. Там еще много чего можно написать. Вы не подумайте, я ни в коем случае не хочу Вас ни коим образом обидеть, но если Вы хотите выжить в эпоху ИИ, то такой код недопустим. Учите матчасть.
Спасибо за отзыв, Snow!
Критика - это всегда весело, особенно такая конструктивная.
Да, код не идеал, но для учебного проекта, имхо, сойдёт.
"Говнокод"? Ну, каждому своё, стараюсь как могу. Есть конкретные правки в статью - готов их принять, давай обсудим.
 
Люди наверное зря придумали все эти ваши ООП, фабрики и прочую поебень.
Сразу вспомнился мем о Hello World с COM...
из STL у Вас только std::vector да и то как невнятная замена обычному массиву. xorData очень легко заменятся лямбдой, а про std::for_each. например, Вы тоже не слышали.
Это не сдача экзамена на знание языка, чувак...
будь я Вашим тимлидом - этот код бы никогда не прошел ревью
...и не тестовое задание на трудоустройство. Не будь таким токсичным мудаком.

Критика - это всегда весело, особенно такая конструктивная.
Нет, это не конструктивная критика, это что-то защемленное.

Статья хорошая. Написана простым языком. Для новичков желающих разобраться в предмете не продираясь через мозговые вывихи автора - то, что нужно. И пусть идут лесом взалкавшие абстракций, фабрик и прочей ООП-мудоты.
 
Спасибо за отзыв, Snow!
Критика - это всегда весело, особенно такая конструктивная.
Да, код не идеал, но для учебного проекта, имхо, сойдёт.
"Говнокод"? Ну, каждому своё, стараюсь как могу. Есть конкретные правки в статью - готов их принять, давай обсудим.
всегда пожалуйста.
Теперь детально.
1) Как учебный проект - это тоже не катит.
Вот пример идеального учебного проекта (ну или близкого к идеальному).
Я почти не нашел к чему придраться, разве только по мелочам.
2) начни с простого - имена переменных. Например,
std::string su(uname.begin(), uname.end()); std::string sp(upass.begin(), upass.end());
вот - не читая комментарии тут без поллитры не разобраться что это за звери. Не стесняйтесь давать переменным длинные имена, благо современные IDE 90% работы по набору текста делают за вас. А читать куда как приятнее.
Или вот еще:
bool sendAll(SocketType s, const char* data, int len) {
ну назови ты ее socketType и мне не придется каждый раз морщить мозг пытаясь вспомнить что это за зверь такой и вообще через "s" чаще всего обозначают строки.
3) используй больше абстракций. Подумай какие сущности за что должны отвечать. Какой у них общий функционал? Как грамотно разнести код по методам, чтобы у тебя не было порнографии в виде дефайнов непосредственно в коде. Банда 4 тебе в помощь. Посмотри в сторону фабрики, фабричного метода, если уж совсем извратиться - погугли за PIMPL. Пока остановимся на этом. Чуть позже дам еще рекомендации
 
Сразу вспомнился мем о Hello World с COM...

Это не сдача экзамена на знание языка, чувак...

...и не тестовое задание на трудоустройство. Не будь таким токсичным мудаком.


Нет, это не конструктивная критика, это что-то защемленное.

Статья хорошая. Написана простым языком. Для новичков желающих разобраться в предмете не продираясь через мозговые вывихи автора - то, что нужно. И пусть идут лесом взалкавшие абстракций, фабрик и прочей ООП-мудоты.
а я разве сказал, что статья плохая? я сказал, что код этот ревью у меня бы не прошел.
Статья хорошая, базовые вещи объясняет достаточно грамотно. Но у меня претензии были именно по реализации. Если мы используем С++, да еще и заявляем минимальный стандарт - так давайте использовать фичи языка. В противном случае это же можно реализовать на С++ 98. Не вижу разницы.
Чуть позже дам более детальные рекомендации по статье.
 
и да, по поводу говнокода - прошу не принимать близко к сердцу. Любой код, по определению, является говнокодом и только спустя десятки итераций анализа и рефакторинга он становится похожим на "совершенный" (Макконел, Мартин вам в помощь). Мой собственный код - тот же самый обыкновенный говнокод, с той только разницей, что благодаря опыту на его приведение в нормальный вид уходит на пару итераций рефакторинга меньше.
 
а я разве сказал, что статья плохая? я сказал, что код этот ревью у меня бы не прошел.
Статья хорошая, базовые вещи объясняет достаточно грамотно. Но у меня претензии были именно по реализации. Если мы используем С++, да еще и заявляем минимальный стандарт - так давайте использовать фичи языка. В противном случае это же можно реализовать на С++ 98. Не вижу разницы.
Чуть позже дам более детальные рекомендации по статье.
Буду писать статьи с упором на C99. Я больше привык на нём кодить. Вчера переписал на него данный небольшой проект, плюс добавил поддержку udp associate.
client:
C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <time.h>

#ifdef _WIN32
  #include <winsock2.h>
  #include <ws2tcpip.h>
  #include <windows.h>
  typedef SOCKET SocketType;
  #define CLOSESOCK closesocket
  #define SOCKERROR WSAGetLastError()

  static bool initSockets(void) {
      WSADATA wd;
      int res = WSAStartup(MAKEWORD(2,2), &wd);
      if (res != 0) {
          fprintf(stderr, "[Client] WSAStartup error=%d\n", res);
          return false;
      }
      return true;
  }

  static void cleanupSockets(void) {
      WSACleanup();
  }

#else
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <arpa/inet.h>
  #include <unistd.h>
  #include <netdb.h>
  #include <fcntl.h>
  #include <pthread.h>
  #include <signal.h>

  typedef int SocketType;
  #define INVALID_SOCKET -1
  #define CLOSESOCK close
  #define SOCKERROR errno

  static bool initSockets(void) {
      return true;
  }

  static void cleanupSockets(void) {
  }
#endif

static void msSleep(unsigned ms) {
   clock_t start_time = clock();
   clock_t wait_ticks = (ms * CLOCKS_PER_SEC) / 1000;
   while (clock() - start_time < wait_ticks);
}

static bool     g_debug         = false;
static char     g_xorKey[256]   = {0};
static char     g_serverIP[64]  = {0};
static uint16_t g_controlPort   = 0;

static void xorData(char *data, int len, const char *key) {
    int keyLen = (int)strlen(key);
    if (keyLen <= 0) return;
    for (int i = 0; i < len; i++) {
        data[i] ^= key[i % keyLen];
    }
}

static bool sendAll(SocketType s, const char *data, int len) {
    int total = 0;
    while (total < len) {
        int sent = (int)send(s, data + total, len - total, 0);
        if (sent <= 0) {
            return false;
        }
        total += sent;
    }
    return true;
}

static bool recvAll(SocketType s, char *buf, int len) {
    int total = 0;
    while (total < len) {
        int r = (int)recv(s, buf + total, len - total, 0);
        if (r <= 0) {
            return false;
        }
        total += r;
    }
    return true;
}

static bool sendEnc(SocketType s, const char *data, int len) {
    char *tmp = (char*)malloc(len);
    if (!tmp) return false;
    memcpy(tmp, data, len);
    xorData(tmp, len, g_xorKey);

    bool ok = sendAll(s, tmp, len);
    free(tmp);
    return ok;
}

static SocketType connectLocal(uint32_t ip_n, uint16_t port_h) {
    SocketType ls = socket(AF_INET, SOCK_STREAM, 0);
    if (ls == INVALID_SOCKET) {
        return INVALID_SOCKET;
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family      = AF_INET;
    addr.sin_addr.s_addr = ip_n;
    addr.sin_port        = htons(port_h);

    if (connect(ls, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        CLOSESOCK(ls);
        return INVALID_SOCKET;
    }
    return ls;
}

static SocketType connectServerEphemeral(const char *ip, uint16_t port) {
    SocketType s = socket(AF_INET, SOCK_STREAM, 0);
    if (s == INVALID_SOCKET) return INVALID_SOCKET;

    struct sockaddr_in a;
    memset(&a, 0, sizeof(a));
    a.sin_family = AF_INET;
    a.sin_port   = htons(port);
    if (inet_pton(AF_INET, ip, &a.sin_addr) <= 0) {
        CLOSESOCK(s);
        return INVALID_SOCKET;
    }
    if (connect(s, (struct sockaddr*)&a, sizeof(a)) < 0) {
        CLOSESOCK(s);
        return INVALID_SOCKET;
    }
    return s;
}

#ifdef _WIN32
static DWORD WINAPI forwardThreadFunc(LPVOID param) {
    SocketType *sockets = (SocketType*)param;
    SocketType esock     = sockets[0];
    SocketType localSock = sockets[1];
    free(param);

    char b[4096];
    for (;;) {
        int rx = (int)recv(esock, b, sizeof(b), 0);
        if (rx <= 0) break;
        if (send(localSock, b, rx, 0) <= 0) break;
    }
    CLOSESOCK(localSock);
    return 0;
}
#else
static void* forwardThreadFunc(void* param) {
    SocketType *sockets = (SocketType*)param;
    SocketType esock     = sockets[0];
    SocketType localSock = sockets[1];
    free(param);

    char b[4096];
    for (;;) {
        int rx = (int)recv(esock, b, sizeof(b), 0);
        if (rx <= 0) break;
        if (send(localSock, b, rx, 0) <= 0) break;
    }
    CLOSESOCK(localSock);
    return NULL;
}
#endif

struct CommandCParams {
    uint16_t ephemeralPort;
    uint32_t targetIP_n;
    uint16_t targetPort_h;
};

static void handleCommandC(uint16_t ephemeralPort, uint32_t targetIP_n, uint16_t targetPort_h) {
    if (g_debug) {
        struct in_addr addrIn;
        addrIn.s_addr = targetIP_n;
        fprintf(stderr, "[Client] handleCommandC: ephemeralPort=%u, target=%s:%u\n",
                ephemeralPort, inet_ntoa(addrIn), targetPort_h);
    }

    SocketType esock = connectServerEphemeral(g_serverIP, ephemeralPort);
    if (esock == INVALID_SOCKET) {
        fprintf(stderr, "[Client] handleCommandC: Failed to connect to ephemeralPort=%u\n",
                ephemeralPort);
        return;
    }
    SocketType localSock = connectLocal(targetIP_n, targetPort_h);
    if (localSock == INVALID_SOCKET) {
        struct in_addr addrIn;
        addrIn.s_addr = targetIP_n;
        fprintf(stderr, "[Client] handleCommandC: Failed to connect locally: %s:%u\n",
                inet_ntoa(addrIn), targetPort_h);
        CLOSESOCK(esock);
        return;
    }
    if (g_debug) {
        fprintf(stderr, "[Client] handleCommandC: connection established\n");
    }

    SocketType *threadParams = (SocketType*)malloc(sizeof(SocketType)*2);
    if (!threadParams) {
        fprintf(stderr, "[Client] OOM in handleCommandC\n");
        CLOSESOCK(localSock);
        CLOSESOCK(esock);
        return;
    }
    threadParams[0] = esock;
    threadParams[1] = localSock;

#ifdef _WIN32
    HANDLE hThread = CreateThread(NULL, 0, forwardThreadFunc, threadParams, 0, NULL);
    if (!hThread) {
        fprintf(stderr, "[Client] CreateThread (TCP) failed\n");
        free(threadParams);
        CLOSESOCK(localSock);
        CLOSESOCK(esock);
        return;
    }
    CloseHandle(hThread);
#else
    pthread_t tid;
    if (pthread_create(&tid, NULL, forwardThreadFunc, threadParams) != 0) {
        fprintf(stderr, "[Client] pthread_create (TCP) failed\n");
        free(threadParams);
        CLOSESOCK(localSock);
        CLOSESOCK(esock);
        return;
    }
    pthread_detach(tid);
#endif

    char b[4096];
    for (;;) {
        int rx = (int)recv(localSock, b, sizeof(b), 0);
        if (rx <= 0) break;
        if (send(esock, b, rx, 0) <= 0) break;
    }
    CLOSESOCK(esock);
    CLOSESOCK(localSock);
    if (g_debug) {
        fprintf(stderr, "[Client] handleCommandC: connection closed\n");
    }
}

struct CommandUParams {
    uint16_t ephemeralPort;
    uint32_t targetIP_n;
    uint16_t targetPort_h;
};

#ifdef _WIN32
static DWORD WINAPI forwardUdpThread(LPVOID param) {
    SocketType *s = (SocketType*)param;
    SocketType sockIn  = s[0];
    SocketType sockOut = s[1];
    free(s);

    char buf[1500];
    for (;;) {
        int n = recv(sockIn, buf, sizeof(buf), 0);
        if (n <= 0) break;
        int sent = send(sockOut, buf, n, 0);
        if (sent <= 0) break;
    }
    return 0;
}
#else
static void* forwardUdpThread(void* param) {
    SocketType *s = (SocketType*)param;
    SocketType sockIn  = s[0];
    SocketType sockOut = s[1];
    free(s);

    char buf[1500];
    for (;;) {
        int n = (int)recv(sockIn, buf, sizeof(buf), 0);
        if (n <= 0) break;
        int sent = (int)send(sockOut, buf, n, 0);
        if (sent <= 0) break;
    }
    return NULL;
}
#endif

static void handleCommandU(uint16_t ephemeralPort, uint32_t targetIP_n, uint16_t targetPort_h) {
    if (g_debug) {
        struct in_addr addrIn;
        addrIn.s_addr = targetIP_n;
        fprintf(stderr, "[Client] handleCommandU: ephemeralPort=%u, target=%s:%u\n",
                ephemeralPort, inet_ntoa(addrIn), targetPort_h);
    }

    SocketType localUdp = socket(AF_INET, SOCK_DGRAM, 0);
    if (localUdp == INVALID_SOCKET) {
        fprintf(stderr, "[Client][UDP] Failed to create local UDP\n");
        return;
    }
    {
        struct sockaddr_in laddr;
        memset(&laddr, 0, sizeof(laddr));
        laddr.sin_family      = AF_INET;
        laddr.sin_port        = 0; /* OS выберет порт */
        laddr.sin_addr.s_addr = htonl(INADDR_ANY);
        if (bind(localUdp, (struct sockaddr*)&laddr, sizeof(laddr)) < 0) {
            fprintf(stderr, "[Client][UDP] bind(local) error\n");
            CLOSESOCK(localUdp);
            return;
        }
    }

    SocketType srvUdp = socket(AF_INET, SOCK_DGRAM, 0);
    if (srvUdp == INVALID_SOCKET) {
        fprintf(stderr, "[Client][UDP] Failed to create server UDP\n");
        CLOSESOCK(localUdp);
        return;
    }
    {
        struct sockaddr_in saddr;
        memset(&saddr, 0, sizeof(saddr));
        saddr.sin_family = AF_INET;
        saddr.sin_port   = htons(ephemeralPort);
        if (inet_pton(AF_INET, g_serverIP, &saddr.sin_addr) <= 0) {
            fprintf(stderr, "[Client][UDP] bad server IP?\n");
            CLOSESOCK(srvUdp);
            CLOSESOCK(localUdp);
            return;
        }
        if (connect(srvUdp, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
            fprintf(stderr, "[Client][UDP] connect(srv) failed\n");
            CLOSESOCK(srvUdp);
            CLOSESOCK(localUdp);
            return;
        }
    }

    SocketType *argA = (SocketType*)malloc(sizeof(SocketType)*2);
    SocketType *argB = (SocketType*)malloc(sizeof(SocketType)*2);
    if (!argA || !argB) {
        fprintf(stderr, "[Client][UDP] Out of memory (malloc)\n");
        if (argA) free(argA);
        if (argB) free(argB);
        CLOSESOCK(srvUdp);
        CLOSESOCK(localUdp);
        return;
    }
    argA[0] = localUdp; argA[1] = srvUdp;
    argB[0] = srvUdp;   argB[1] = localUdp;

#ifdef _WIN32
    HANDLE h1 = CreateThread(NULL, 0, forwardUdpThread, argA, 0, NULL);
    if (!h1) {
        fprintf(stderr, "[Client][UDP] CreateThread(A) failed\n");
        free(argA); /* поток не запустился => никто не освободит argA */
        free(argB);
        CLOSESOCK(srvUdp);
        CLOSESOCK(localUdp);
        return;
    }
    HANDLE h2 = CreateThread(NULL, 0, forwardUdpThread, argB, 0, NULL);
    if (!h2) {
        fprintf(stderr, "[Client][UDP] CreateThread(B) failed\n");
        /* Принудительно убиваем h1, т.к. оно уже запущено и использует localUdp/srvUdp. */
        TerminateThread(h1, 1);
        CloseHandle(h1);

        free(argB);
        CLOSESOCK(srvUdp);
        CLOSESOCK(localUdp);
        return;
    }
    CloseHandle(h1);
    CloseHandle(h2);

#else
    pthread_t t1, t2;
    if (pthread_create(&t1, NULL, forwardUdpThread, argA) != 0) {
        fprintf(stderr, "[Client][UDP] pthread_create(A) failed\n");
        free(argA);
        free(argB);
        CLOSESOCK(srvUdp);
        CLOSESOCK(localUdp);
        return;
    }
    if (pthread_create(&t2, NULL, forwardUdpThread, argB) != 0) {
        fprintf(stderr, "[Client][UDP] pthread_create(B) failed\n");
        pthread_cancel(t1);
        pthread_join(t1, NULL);

        free(argB);
        CLOSESOCK(srvUdp);
        CLOSESOCK(localUdp);
        return;
    }
    pthread_detach(t1);
    pthread_detach(t2);
#endif

    if (g_debug) {
        fprintf(stderr, "[Client][UDP] handleCommandU: done => localUdp<->srvUdp started\n");
    }
}

#ifdef _WIN32
static DWORD WINAPI keepAliveThreadFunc(LPVOID param) {
    SocketType ctrlSock = (SocketType)(uintptr_t)param;
    for (;;) {
        msSleep(15000);
        char c = 'K';
        if (strlen(g_xorKey) > 0) {
            c ^= g_xorKey[0];
        }
        if (send(ctrlSock, &c, 1, 0) <= 0) {
            if (g_debug) {
                fprintf(stderr, "[Client] keepAliveThread: send fail => exit\n");
            }
            break;
        }
        if (g_debug) {
            fprintf(stderr, "[Client] keepAliveThread: 'K' sent\n");
        }
    }
    return 0;
}
#else
static void* keepAliveThreadFunc(void* param) {
    SocketType ctrlSock = (SocketType)(uintptr_t)param;
    for (;;) {
        msSleep(15000);
        char c = 'K';
        if (strlen(g_xorKey) > 0) {
            c ^= g_xorKey[0];
        }
        if (send(ctrlSock, &c, 1, 0) <= 0) {
            if (g_debug) {
                fprintf(stderr, "[Client] keepAliveThread: send fail => exit\n");
            }
            break;
        }
        if (g_debug) {
            fprintf(stderr, "[Client] keepAliveThread: 'K' sent\n");
        }
    }
    return NULL;
}
#endif

#ifndef _WIN32
static void* commandCThread(void* param) {
    struct CommandCParams *pp = (struct CommandCParams*)param;
    handleCommandC(pp->ephemeralPort, pp->targetIP_n, pp->targetPort_h);
    free(pp);
    return NULL;
}
static void* commandUThread(void* param) {
    struct CommandUParams *pp = (struct CommandUParams*)param;
    handleCommandU(pp->ephemeralPort, pp->targetIP_n, pp->targetPort_h);
    free(pp);
    return NULL;
}
#else
static DWORD WINAPI commandCThreadWin(LPVOID param) {
    struct CommandCParams *pp = (struct CommandCParams*)param;
    handleCommandC(pp->ephemeralPort, pp->targetIP_n, pp->targetPort_h);
    free(pp);
    return 0;
}
static DWORD WINAPI commandUThreadWin(LPVOID param) {
    struct CommandUParams *pp = (struct CommandUParams*)param;
    handleCommandU(pp->ephemeralPort, pp->targetIP_n, pp->targetPort_h);
    free(pp);
    return 0;
}
#endif

static void controlChannelLoop(SocketType ctrl) {
    if (g_debug) {
        fprintf(stderr, "[Client] Sending HELLO\n");
    }
    {
        char hello[5] = {'H','E','L','L','O'};
        if (!sendEnc(ctrl, hello, 5)) {
            if (g_debug) {
                fprintf(stderr, "[Client] failed to send HELLO\n");
            }
            return;
        }
    }
    {
        char resp[5] = {0};
        int r = (int)recv(ctrl, resp, 5, 0);
        if (r <= 0) {
            if (g_debug) {
                fprintf(stderr, "[Client] failed to read HELLO response\n");
            }
            return;
        }
        for (int i = 0; i < r; i++) {
            resp[i] ^= g_xorKey[i % strlen(g_xorKey)];
        }
        if (r == 5 && memcmp(resp, "OCCUP", 5) == 0) {
            fprintf(stderr, "[Client] The server is already used by another NAT client.\n");
            return;
        } else if (r == 2 && memcmp(resp, "OK", 2) == 0) {
            printf("[Client] XOR-handshake succeeded (received 'OK')\n");
        } else {
            if (g_debug) {
                fprintf(stderr, "[Client] expected 'OK', got '%.*s'\n", r, resp);
            }
            return;
        }
    }

    /* keep-alive поток */
#ifdef _WIN32
    {
        HANDLE hKA = CreateThread(NULL, 0, keepAliveThreadFunc, (LPVOID)(uintptr_t)ctrl, 0, NULL);
        if (!hKA) {
            fprintf(stderr, "[Client] Failed to create keepAliveThread\n");
            return;
        }
        CloseHandle(hKA);
    }
#else
    {
        pthread_t kaThread;
        if (pthread_create(&kaThread, NULL, keepAliveThreadFunc, (void*)(uintptr_t)ctrl) == 0) {
            pthread_detach(kaThread);
        } else {
            fprintf(stderr, "[Client] Failed to create keepAliveThread\n");
            return;
        }
    }
#endif

    while (1) {
        char cmd;
        int rc = (int)recv(ctrl, &cmd, 1, 0);
        if (rc <= 0) {
            if (g_debug) {
                fprintf(stderr, "[Client] Control channel disconnected\n");
            }
            break;
        }
        if (strlen(g_xorKey) > 0) {
            cmd ^= g_xorKey[0];
        }
        if (cmd == 'C') {
            char buf[8];
            if (!recvAll(ctrl, buf, 8)) {
                if (g_debug) {
                    fprintf(stderr, "[Client] Error reading 'C' data\n");
                }
                break;
            }
            for (int i = 0; i < 8; i++) {
                buf[i] ^= g_xorKey[(1 + i) % strlen(g_xorKey)];
            }
            uint16_t ep_n, tp_n;
            uint32_t tip_n;
            memcpy(&ep_n,  buf,   2);
            memcpy(&tip_n, buf+2, 4);
            memcpy(&tp_n,  buf+6, 2);

            struct CommandCParams *p = (struct CommandCParams*)malloc(sizeof(*p));
            if (!p) {
                fprintf(stderr, "[Client] OOM in 'C'\n");
                continue;
            }
            p->ephemeralPort = ntohs(ep_n);
            p->targetIP_n    = tip_n;
            p->targetPort_h  = ntohs(tp_n);

#ifdef _WIN32
            HANDLE th = CreateThread(NULL, 0, commandCThreadWin, p, 0, NULL);
            if (th) CloseHandle(th); else free(p);
#else
            pthread_t tid;
            if (pthread_create(&tid, NULL, commandCThread, p) == 0) {
                pthread_detach(tid);
            } else {
                free(p);
            }
#endif
        }
        else if (cmd == 'U') {
            /* UDP */
            char buf[8];
            if (!recvAll(ctrl, buf, 8)) {
                if (g_debug) {
                    fprintf(stderr, "[Client] Error reading 'U' data\n");
                }
                break;
            }
            for (int i = 0; i < 8; i++) {
                buf[i] ^= g_xorKey[(1 + i) % strlen(g_xorKey)];
            }
            uint16_t ep_n, tp_n;
            uint32_t tip_n;
            memcpy(&ep_n,  buf,   2);
            memcpy(&tip_n, buf+2, 4);
            memcpy(&tp_n,  buf+6, 2);

            struct CommandUParams *p = (struct CommandUParams*)malloc(sizeof(*p));
            if (!p) {
                fprintf(stderr, "[Client] OOM in 'U'\n");
                continue;
            }
            p->ephemeralPort = ntohs(ep_n);
            p->targetIP_n    = tip_n;
            p->targetPort_h  = ntohs(tp_n);

#ifdef _WIN32
            HANDLE th = CreateThread(NULL, 0, commandUThreadWin, p, 0, NULL);
            if (th) CloseHandle(th); else free(p);
#else
            pthread_t tid;
            if (pthread_create(&tid, NULL, commandUThread, p) == 0) {
                pthread_detach(tid);
            } else {
                free(p);
            }
#endif
        }
        else {
            if (g_debug) {
                fprintf(stderr, "[Client] Unknown cmd=%c\n", cmd);
            }
        }
    }

    CLOSESOCK(ctrl);
    if (g_debug) {
        fprintf(stderr, "[Client] controlChannelLoop: exit\n");
    }
}

static void runClientLoop(void) {
    for (;;) {
        SocketType s = socket(AF_INET, SOCK_STREAM, 0);
        if (s == INVALID_SOCKET) {
            fprintf(stderr, "[Client] socket() error\n");
            msSleep(5000);
            continue;
        }
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_port   = htons(g_controlPort);
        if (inet_pton(AF_INET, g_serverIP, &addr.sin_addr) <= 0) {
            fprintf(stderr, "[Client] Invalid server IP: %s\n", g_serverIP);
            CLOSESOCK(s);
            msSleep(5000);
            continue;
        }
        if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
            fprintf(stderr, "[Client] Failed connect to %s:%u. Retry 5s.\n",
                    g_serverIP, g_controlPort);
            CLOSESOCK(s);
            msSleep(5000);
            continue;
        }
        printf("[Client] Connected to %s:%u\n", g_serverIP, g_controlPort);

        controlChannelLoop(s);

        fprintf(stderr, "[Client] Control connection closed. Retry in 5s...\n");
        msSleep(5000);
    }
}

static void printUsage(const char* prog) {
    printf("Usage (Client):\n"
           "  %s -s <server_ip> -c <control_port> -x <xor_key> [-d]\n"
           "Example:\n"
           "  %s -s 1.2.3.4 -c 9000 -x secret -d\n",
           prog, prog);
}

int main(int argc, char* argv[]) {
    if (!initSockets()) {
        return 1;
    }
#ifndef _WIN32
    signal(SIGPIPE, SIG_IGN);
#endif

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-s") == 0) {
            if (i+1 >= argc) {
                printUsage(argv[0]);
                cleanupSockets();
                return 0;
            }
            strncpy(g_serverIP, argv[++i], sizeof(g_serverIP)-1);
            g_serverIP[sizeof(g_serverIP)-1] = '\0';
        }
        else if (strcmp(argv[i], "-c") == 0) {
            if (i+1 >= argc) {
                printUsage(argv[0]);
                cleanupSockets();
                return 0;
            }
            g_controlPort = (uint16_t)atoi(argv[++i]);
        }
        else if (strcmp(argv[i], "-x") == 0) {
            if (i+1 >= argc) {
                printUsage(argv[0]);
                cleanupSockets();
                return 0;
            }
            strncpy(g_xorKey, argv[++i], sizeof(g_xorKey)-1);
            g_xorKey[sizeof(g_xorKey)-1] = '\0';
        }
        else if (strcmp(argv[i], "-d") == 0) {
            g_debug = true;
        }
        else {
            printUsage(argv[0]);
            cleanupSockets();
            return 0;
        }
    }
    if (strlen(g_serverIP) == 0 || g_controlPort == 0 || strlen(g_xorKey) == 0) {
        printUsage(argv[0]);
        cleanupSockets();
        return 0;
    }

    runClientLoop();

    cleanupSockets();
    return 0;
}
server:
C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#ifdef _WIN32
  #include <winsock2.h>
  #include <ws2tcpip.h>
  #include <windows.h>
  #pragma comment(lib, "ws2_32.lib")
  typedef SOCKET SocketType;
  #define CLOSESOCK closesocket
  #define SOCKERROR WSAGetLastError()
  #define INVALID_SOCKET_VAL INVALID_SOCKET
#else
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <arpa/inet.h>
  #include <unistd.h>
  #include <netdb.h>
  #include <errno.h>
  #include <sys/time.h>
  #include <signal.h>

  typedef int SocketType;
  #define CLOSESOCK close
  #define INVALID_SOCKET_VAL -1
  #define SOCKERROR errno
#endif

#ifdef _WIN32
  static CRITICAL_SECTION g_mutex;
  static void init_mutex() { InitializeCriticalSection(&g_mutex); }
  static void lock_mutex() { EnterCriticalSection(&g_mutex); }
  static void unlock_mutex() { LeaveCriticalSection(&g_mutex); }
  static void destroy_mutex() { DeleteCriticalSection(&g_mutex); }

  typedef HANDLE thread_t;
  typedef struct {
      void *(*func)(void*);
      void *arg;
  } thread_ctx;

  static DWORD WINAPI thread_func_wrapper(LPVOID param) {
      thread_ctx *ctx = (thread_ctx*)param;
      ctx->func(ctx->arg);
      free(ctx);
      return 0;
  }

  static thread_t create_thread(void *(*func)(void*), void *arg) {
      thread_ctx *ctx = (thread_ctx*)malloc(sizeof(thread_ctx));
      if(!ctx) return NULL;
      ctx->func = func;
      ctx->arg = arg;
      return CreateThread(NULL, 0, thread_func_wrapper, ctx, 0, NULL);
  }

  static void join_thread(thread_t t) {
      WaitForSingleObject(t, INFINITE);
      CloseHandle(t);
  }

  static void detach_thread(thread_t t) {
      CloseHandle(t);
  }

#else
  #include <pthread.h>
  static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
  static void init_mutex() {  }
  static void lock_mutex() { pthread_mutex_lock(&g_mutex); }
  static void unlock_mutex() { pthread_mutex_unlock(&g_mutex); }
  static void destroy_mutex() { pthread_mutex_destroy(&g_mutex); }

  typedef pthread_t thread_t;

  static void *thread_func_wrapper(void *param) {
      void **arr = (void**)param;
      void *(*func)(void*) = (void*(*)(void*))arr[0];
      void *real_arg = arr[1];
      free(arr);
      func(real_arg);
      return NULL;
  }

  static thread_t create_thread(void *(*func)(void*), void *arg) {
      pthread_t t;
      void **arr = (void**)malloc(sizeof(void*) * 2);
      if(!arr) return 0;
      arr[0] = (void*)func;
      arr[1] = arg;
      if(pthread_create(&t, NULL, thread_func_wrapper, arr) != 0) {
          free(arr);
          return 0;
      }
      return t;
  }

  static void join_thread(thread_t t) {
      pthread_join(t, NULL);
  }

  static void detach_thread(thread_t t) {
      pthread_detach(t);
  }
#endif

static const int BACKLOG = 10;
static const int BUFFER_SIZE = 4096;

static int g_debug = 0;

static SocketType g_natClientSock = INVALID_SOCKET_VAL;
static int g_natClientConnected   = 0;

#define MAX_STR 256
static char  g_xorKey[MAX_STR]    = {0};
static int   g_xorKeyLen         = 0;
static char  g_socksUser[MAX_STR] = {0};
static char  g_socksPass[MAX_STR] = {0};

static int initSockets(void) {
#ifdef _WIN32
    WSADATA wd;
    int res = WSAStartup(MAKEWORD(2,2), &wd);
    if(res != 0) {
        fprintf(stderr, "[Server] WSAStartup error=%d\n", res);
        return 0;
    }
#endif
    return 1;
}
static void cleanupSockets(void) {
#ifdef _WIN32
    WSACleanup();
#endif
}

static int sendAll(SocketType s, const char* data, int len) {
    int total = 0;
    while(total < len) {
        int sent = send(s, data + total, len - total, 0);
        if(sent <= 0) return 0;
        total += sent;
    }
    return 1;
}
static int recvAll(SocketType s, char* buf, int len) {
    int total = 0;
    while(total < len) {
        int r = recv(s, buf + total, len - total, 0);
        if(r <= 0) return 0;
        total += r;
    }
    return 1;
}

static int sendEnc(SocketType s, const char* data, int len) {
    if(s == INVALID_SOCKET_VAL) return 0;
    if(g_xorKeyLen <= 0) {
        return sendAll(s, data, len);
    }
    if(len > BUFFER_SIZE) {
        char *tmpDyn = (char*)malloc(len);
        if(!tmpDyn) return 0;
        for(int i=0; i<len; i++){
            tmpDyn[i] = data[i] ^ g_xorKey[i % g_xorKeyLen];
        }
        int ok = sendAll(s, tmpDyn, len);
        free(tmpDyn);
        return ok;
    } else {
        char tmp[BUFFER_SIZE];
        for(int i=0; i<len; i++){
            tmp[i] = data[i] ^ g_xorKey[i % g_xorKeyLen];
        }
        return sendAll(s, tmp, len);
    }
}

static SocketType createListeningSocket(uint16_t port) {
    SocketType sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock == INVALID_SOCKET_VAL) {
        fprintf(stderr, "[Server] socket() error=%d\n", SOCKERROR);
        return INVALID_SOCKET_VAL;
    }
    {
        int opt = 1;
#ifdef _WIN32
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
#else
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family      = AF_INET;
    addr.sin_port        = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    if(bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        fprintf(stderr, "[Server] bind error=%d\n", SOCKERROR);
        CLOSESOCK(sock);
        return INVALID_SOCKET_VAL;
    }
    if(listen(sock, BACKLOG) < 0) {
        fprintf(stderr, "[Server] listen error=%d\n", SOCKERROR);
        CLOSESOCK(sock);
        return INVALID_SOCKET_VAL;
    }
    return sock;
}

static const unsigned char VER_SOCKS5     = 0x05;
static const unsigned char METHOD_NOAUTH  = 0x00;
static const unsigned char METHOD_USERPASS= 0x02;
static const unsigned char METHOD_REJECT  = 0xFF;

static const unsigned char CMD_CONNECT    = 0x01;
static const unsigned char CMD_UDP_ASSOC  = 0x03;


static int socks5Handshake_SelectMethod(SocketType s, int *useUserPassOut) {
    unsigned char hdr[2];
    int r = recv(s, (char*)hdr, 2, 0);
    if(r < 2) return 0;
    if(hdr[0] != VER_SOCKS5) return 0;

    int nMethods = hdr[1];
    if(nMethods < 0 || nMethods > 255) return 0;

    unsigned char methods[256];
    r = recv(s, (char*)methods, nMethods, 0);
    if(r < nMethods) return 0;

    int haveUserPass = (strlen(g_socksUser)>0 || strlen(g_socksPass)>0)?1:0;
    *useUserPassOut = 0;

    if(haveUserPass) {
        int found = 0;
        for(int i=0; i<nMethods; i++){
            if(methods[i] == METHOD_USERPASS) {
                found = 1;
                break;
            }
        }
        if(!found) {
            unsigned char resp[2] = {VER_SOCKS5, METHOD_REJECT};
            sendAll(s, (char*)resp, 2);
            return 0;
        }
        *useUserPassOut = 1;
        {
            unsigned char resp[2] = {VER_SOCKS5, METHOD_USERPASS};
            sendAll(s, (char*)resp, 2);
        }
    } else {
        int found = 0;
        for(int i=0; i<nMethods; i++){
            if(methods[i] == METHOD_NOAUTH) {
                found = 1;
                break;
            }
        }
        if(!found) {
            unsigned char resp[2] = {VER_SOCKS5, METHOD_REJECT};
            sendAll(s, (char*)resp, 2);
            return 0;
        }
        {
            unsigned char resp[2] = {VER_SOCKS5, METHOD_NOAUTH};
            sendAll(s, (char*)resp, 2);
        }
    }
    return 1;
}

static int socks5Handshake_UserPass(SocketType s) {
    unsigned char ver;
    if(recv(s, (char*)&ver, 1, 0) < 1) return 0;
    if(ver != 0x01) return 0;

    unsigned char ulen;
    if(recv(s, (char*)&ulen, 1, 0) < 1) return 0;

    char uname[256];
    if(!recvAll(s, uname, ulen)) return 0;
    uname[ulen] = '\0';

    unsigned char plen;
    if(recv(s, (char*)&plen, 1, 0) < 1) return 0;

    char upass[256];
    if(!recvAll(s, upass, plen)) return 0;
    upass[plen] = '\0';

    if(strcmp(uname, g_socksUser)!=0 || strcmp(upass, g_socksPass)!=0) {
        unsigned char resp[2] = {0x01, 0x01};
        sendAll(s, (char*)resp, 2);
        return 0;
    }
    {
        unsigned char resp[2] = {0x01, 0x00};
        sendAll(s, (char*)resp, 2);
    }
    return 1;
}

struct Socks5Req {
    unsigned char ver;
    unsigned char cmd;
    unsigned char rsv;
    unsigned char atyp;
};

static int socks5ParseHeader(SocketType s, struct Socks5Req *req) {
    unsigned char hdr[4];
    if(!recvAll(s, (char*)hdr, 4)) return 0;
    req->ver  = hdr[0];
    req->cmd  = hdr[1];
    req->rsv  = hdr[2];
    req->atyp = hdr[3];
    if(req->ver != VER_SOCKS5) return 0;
    return 1;
}

static void socks5SendConnectReply(SocketType s, unsigned char rep, uint32_t ip, uint16_t port) {
    unsigned char buf[10];
    buf[0] = 0x05;   /* VER */
    buf[1] = rep;    /* REP */
    buf[2] = 0x00;   /* RSV */
    buf[3] = 0x01;   /* ATYP=IPv4 */
    {
        uint32_t ip_n = htonl(ip);
        memcpy(buf+4, &ip_n, 4);
    }
    {
        uint16_t p_n = htons(port);
        memcpy(buf+8, &p_n, 2);
    }
    sendAll(s, (char*)buf, 10);
}

static int socks5ParseConnectTarget(SocketType s, unsigned char atyp, uint32_t *ipOut, uint16_t *portOut) {
    if(atyp == 0x01) {
        unsigned char a4[4];
        if(!recvAll(s, (char*)a4, 4)) return 0;
        uint32_t ip = ((uint32_t)a4[0]<<24) | ((uint32_t)a4[1]<<16) | ((uint32_t)a4[2]<<8) | (uint32_t)a4[3];
        unsigned char pbuf[2];
        if(!recvAll(s, (char*)pbuf, 2)) return 0;
        uint16_t port = ((uint16_t)pbuf[0]<<8) | (uint16_t)pbuf[1];
        *ipOut   = ip;
        *portOut = port;
        return 1;
    } else if(atyp == 0x03) {
        unsigned char dlen;
        if(!recvAll(s, (char*)&dlen, 1)) return 0;
        char dom[256];
        if(!recvAll(s, dom, dlen)) return 0;
        dom[dlen] = '\0';

        unsigned char pbuf[2];
        if(!recvAll(s, (char*)pbuf, 2)) return 0;
        uint16_t port = ((uint16_t)pbuf[0]<<8) | (uint16_t)pbuf[1];

        struct hostent* he = gethostbyname(dom);
        if(!he) return 0;
        struct in_addr **alist = (struct in_addr**)he->h_addr_list;
        if(!alist || !alist[0]) return 0;
        uint32_t ip = ntohl(alist[0]->s_addr);

        *ipOut   = ip;
        *portOut = port;
        return 1;
    }
    return 0;
}

struct forward_args {
    SocketType src;
    SocketType dst;
};
static void *forwardThreadFunc(void *arg) {
    struct forward_args *fa = (struct forward_args*)arg;
    char buffer[BUFFER_SIZE];
    while(1) {
        int rx = recv(fa->src, buffer, BUFFER_SIZE, 0);
        if(rx <= 0) break;
        int tx = send(fa->dst, buffer, rx, 0);
        if(tx <= 0) break;
    }
    CLOSESOCK(fa->dst);
    return NULL;
}

static int handleSocksClientTCP(SocketType sock, unsigned char atyp) {
    uint32_t tip   = 0;
    uint16_t tport = 0;
    if(!socks5ParseConnectTarget(sock, atyp, &tip, &tport)) {
        if(g_debug) {
            fprintf(stderr, "[Server] parse CONNECT fail\n");
        }
        socks5SendConnectReply(sock, 0x01, 0, 0);
        return 0;
    }

    SocketType epSock = socket(AF_INET, SOCK_STREAM, 0);
    if(epSock == INVALID_SOCKET_VAL) {
        socks5SendConnectReply(sock, 0x01, 0, 0);
        return 0;
    }
    struct sockaddr_in ep;
    memset(&ep, 0, sizeof(ep));
    ep.sin_family      = AF_INET;
    ep.sin_port        = 0;
    ep.sin_addr.s_addr = INADDR_ANY;
    if(bind(epSock, (struct sockaddr*)&ep, sizeof(ep))<0 ||
       listen(epSock,1)<0) {
        socks5SendConnectReply(sock, 0x01, 0, 0);
        CLOSESOCK(epSock);
        return 0;
    }

    struct sockaddr_in tmp;
    socklen_t sz = sizeof(tmp);
    getsockname(epSock, (struct sockaddr*)&tmp, &sz);
    uint16_t ephemeralPort = ntohs(tmp.sin_port);

    if(g_debug) {
        fprintf(stderr, "[Server] TCP CONNECT to %u.%u.%u.%u:%u, ephemeralPort=%u\n",
                (tip>>24)&0xFF, (tip>>16)&0xFF, (tip>>8)&0xFF, tip&0xFF,
                (unsigned)tport, (unsigned)ephemeralPort);
    }

    int okSend = 0;
    lock_mutex();
    if(g_natClientSock != INVALID_SOCKET_VAL && g_natClientConnected) {
        char cmd[1+2+4+2];
        cmd[0] = 'C';
        {
            uint16_t ep_n = htons(ephemeralPort);
            memcpy(cmd+1, &ep_n, 2);
        }
        {
            uint32_t tip_n = htonl(tip);
            memcpy(cmd+3, &tip_n, 4);
        }
        {
            uint16_t tp_n = htons(tport);
            memcpy(cmd+7, &tp_n, 2);
        }
        okSend = sendEnc(g_natClientSock, cmd, sizeof(cmd));
    }
    unlock_mutex();

    if(!okSend) {
        if(g_debug) {
            fprintf(stderr, "[Server] Could not send 'C' command to NAT client\n");
        }
        socks5SendConnectReply(sock, 0x05, 0, 0);
        CLOSESOCK(epSock);
        return 0;
    }

    struct sockaddr_in from;
    socklen_t flen = sizeof(from);
    SocketType esock = accept(epSock, (struct sockaddr*)&from, &flen);
    CLOSESOCK(epSock);
    if(esock == INVALID_SOCKET_VAL) {
        if(g_debug) {
            fprintf(stderr, "[Server] ephemeral accept fail\n");
        }
        socks5SendConnectReply(sock, 0x05, 0, 0);
        return 0;
    }

    socks5SendConnectReply(sock, 0x00, tip, tport);

    struct forward_args *fa = (struct forward_args*)malloc(sizeof(struct forward_args));
    if(!fa) {
        CLOSESOCK(esock);
        return 0;
    }
    fa->src = sock;
    fa->dst = esock;
    thread_t tFwd = create_thread(forwardThreadFunc, fa);

    {
        char b[BUFFER_SIZE];
        while(1) {
            int rx = recv(esock, b, BUFFER_SIZE, 0);
            if(rx<=0) break;
            if(send(sock, b, rx, 0)<=0) break;
        }
    }
    CLOSESOCK(esock);
    join_thread(tFwd);
    return 1;
}



struct udp_assoc_args {
    SocketType tcpSock;
    SocketType udpSock;
    struct sockaddr_in clientAddr;
    int clientAddrLen;
    int running;
};

static void *udpRelayThread(void *arg) {
    struct udp_assoc_args *ua = (struct udp_assoc_args*)arg;

    if(g_debug) {
        fprintf(stderr, "[Server][UDP] Relay thread started\n");
    }

    struct sockaddr_in remoteAddr;
    int haveRemote = 0;

    while(ua->running) {
        char buf[1500];
        struct sockaddr_in fromAddr;
        socklen_t fromLen = sizeof(fromAddr);
        int n = recvfrom(ua->udpSock, buf, sizeof(buf), 0,
                         (struct sockaddr*)&fromAddr, &fromLen);
        if(n <= 0) {
#ifdef _WIN32
            if(SOCKERROR == WSAEINTR || SOCKERROR == WSAECONNRESET)
                continue;
#else
            if(SOCKERROR == EINTR)
                continue;
#endif
            if(g_debug) {
                fprintf(stderr, "[Server][UDP] recvfrom error=%d => exit thread\n", SOCKERROR);
            }
            break;
        }

        int isFromClient = (n >= 10 && (uint8_t)buf[0] == 0x00 && (uint8_t)buf[1] == 0x00);

        if(isFromClient) {
            if(g_debug) {
                fprintf(stderr, "[Server][UDP] Packet from client detected\n");
            }
            if(buf[2] != 0x00 || buf[3] != 0x01) {
                if(g_debug) {
                    fprintf(stderr, "[Server][UDP] ignoring FRAG != 0 or ATYP != IPv4\n");
                }
                continue;
            }
            uint32_t dstIP = (((uint8_t)buf[4] << 24) | ((uint8_t)buf[5] << 16) |
                              ((uint8_t)buf[6] << 8)  | (uint8_t)buf[7]);
            uint16_t dstPort = (((uint8_t)buf[8] << 8) | (uint8_t)buf[9]);
            char *payload = buf + 10;
            int payloadLen = n - 10;
            
            if(!haveRemote) {
                memset(&remoteAddr, 0, sizeof(remoteAddr));
                remoteAddr.sin_family = AF_INET;
                remoteAddr.sin_addr.s_addr = htonl(dstIP);
                remoteAddr.sin_port = htons(dstPort);
                haveRemote = 1;
                if(g_debug) {
                    fprintf(stderr, "[Server][UDP] Set remote to %u.%u.%u.%u:%u\n",
                        (dstIP >> 24) & 0xFF, (dstIP >> 16) & 0xFF,
                        (dstIP >> 8) & 0xFF, dstIP & 0xFF, dstPort);
                }
            }
            int sent = sendto(ua->udpSock, payload, payloadLen, 0,
                              (struct sockaddr*)&remoteAddr, sizeof(remoteAddr));
            if(sent < 0 && g_debug) {
                fprintf(stderr, "[Server][UDP] sendto remote error=%d\n", SOCKERROR);
            }
        } else {
            if(!haveRemote) {
                if(g_debug) {
                    fprintf(stderr, "[Server][UDP] Received packet from unknown remote, ignoring\n");
                }
                continue;
            }
            uint8_t outBuf[1500];
            int outLen = 0;
            outBuf[0] = 0x00;
            outBuf[1] = 0x00;
            outBuf[2] = 0x00; // FRAG = 0
            outBuf[3] = 0x01; // ATYP = IPv4
            uint32_t ip_h = ntohl(fromAddr.sin_addr.s_addr);
            outBuf[4] = (ip_h >> 24) & 0xFF;
            outBuf[5] = (ip_h >> 16) & 0xFF;
            outBuf[6] = (ip_h >> 8) & 0xFF;
            outBuf[7] = ip_h & 0xFF;
            uint16_t p_h = ntohs(fromAddr.sin_port);
            outBuf[8] = (p_h >> 8) & 0xFF;
            outBuf[9] = p_h & 0xFF;
            if(n > (int)(sizeof(outBuf) - 10))
                n = (int)(sizeof(outBuf) - 10);
            memcpy(outBuf + 10, buf, n);
            outLen = 10 + n;
            int sent = sendto(ua->udpSock, (char*)outBuf, outLen, 0,
                              (struct sockaddr*)&ua->clientAddr, ua->clientAddrLen);
            if(sent < 0 && g_debug) {
                fprintf(stderr, "[Server][UDP] sendto client error=%d\n", SOCKERROR);
            }
        }
    }

    if(g_debug) {
        fprintf(stderr, "[Server][UDP] Relay thread finished\n");
    }
    return NULL;
}

static int handleSocksClientUDP(SocketType sock, unsigned char atyp,
                                struct sockaddr_in *pClientAddr, socklen_t addrLen)
{
    uint32_t dummyIP=0; uint16_t dummyPort=0;
    if(!socks5ParseConnectTarget(sock, atyp, &dummyIP, &dummyPort)) {
        socks5SendConnectReply(sock, 0x01, 0, 0);
        return 0;
    }
    SocketType udpSock = socket(AF_INET, SOCK_DGRAM, 0);
    if(udpSock == INVALID_SOCKET_VAL) {
        socks5SendConnectReply(sock, 0x01, 0, 0);
        return 0;
    }
    struct sockaddr_in udpAddr;
    memset(&udpAddr, 0, sizeof(udpAddr));
    udpAddr.sin_family      = AF_INET;
    udpAddr.sin_addr.s_addr = INADDR_ANY;
    udpAddr.sin_port        = 0;
    if(bind(udpSock, (struct sockaddr*)&udpAddr, sizeof(udpAddr))<0) {
        CLOSESOCK(udpSock);
        socks5SendConnectReply(sock, 0x01, 0, 0);
        return 0;
    }
    socklen_t len2 = sizeof(udpAddr);
    getsockname(udpSock, (struct sockaddr*)&udpAddr, &len2);
    uint16_t udpPort = ntohs(udpAddr.sin_port);

    if(g_debug) {
        fprintf(stderr, "[Server] UDP ASSOCIATE => local port = %u\n", (unsigned)udpPort);
    }

    socks5SendConnectReply(sock, 0x00, 0 /*0.0.0.0*/, udpPort);

    struct udp_assoc_args *ua = (struct udp_assoc_args*)malloc(sizeof(*ua));
    if(!ua) {
        CLOSESOCK(udpSock);
        return 0;
    }
    ua->tcpSock   = sock;
    ua->udpSock   = udpSock;
    ua->clientAddr = *pClientAddr;
    ua->clientAddrLen = addrLen;
    ua->running   = 1;

    thread_t th = create_thread(udpRelayThread, ua);

    {
        char tmpBuf[1];
        while(1) {
            int r = recv(sock, tmpBuf, 1, 0);
            if(r<=0) {
                if(g_debug) {
                    fprintf(stderr, "[Server] UDP Associate: TCP disconnected => stop\n");
                }
                break;
            }

        }
    }
    ua->running = 0;
    CLOSESOCK(udpSock);
    join_thread(th);
    free(ua);
    return 1;
}

static void *handleSocksClient(void *arg) {
    SocketType sock = (SocketType)(uintptr_t)arg;
    if(g_debug) {
        fprintf(stderr, "[Server] SOCKS client: start\n");
    }
    int useUserPass = 0;
    if(!socks5Handshake_SelectMethod(sock, &useUserPass)) {
        if(g_debug) {
            fprintf(stderr, "[Server] handshake fail\n");
        }
        CLOSESOCK(sock);
        return NULL;
    }
    if(useUserPass) {
        if(!socks5Handshake_UserPass(sock)) {
            if(g_debug) {
                fprintf(stderr, "[Server] user/pass fail\n");
            }
            CLOSESOCK(sock);
            return NULL;
        }
    }

    struct Socks5Req req;
    if(!socks5ParseHeader(sock, &req)) {
        if(g_debug) {
            fprintf(stderr, "[Server] parseHeader fail\n");
        }
        CLOSESOCK(sock);
        return NULL;
    }

    struct sockaddr_in clientAddr;
    socklen_t addrLen = sizeof(clientAddr);
    getpeername(sock, (struct sockaddr*)&clientAddr, &addrLen);

    if(req.cmd == CMD_CONNECT) {
        if(!handleSocksClientTCP(sock, req.atyp)) {
        }
        CLOSESOCK(sock);
    }
    else if(req.cmd == CMD_UDP_ASSOC) {
        handleSocksClientUDP(sock, req.atyp, &clientAddr, addrLen);
        CLOSESOCK(sock);
    }
    else {
        if(g_debug) {
            fprintf(stderr, "[Server] unsupported CMD=%d\n", (int)req.cmd);
        }
        socks5SendConnectReply(sock, 0x07 /* X'07' Command not supported */, 0, 0);
        CLOSESOCK(sock);
    }

    if(g_debug) {
        fprintf(stderr, "[Server] SOCKS client: done\n");
    }
    return NULL;
}

static int recvAllWithTimeout(SocketType s, char* buf, int len, int timeoutSec) {
#ifdef _WIN32
    DWORD tmo = timeoutSec * 1000;
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tmo, sizeof(tmo));
#else
    struct timeval tv;
    tv.tv_sec  = timeoutSec;
    tv.tv_usec = 0;
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv));
#endif

    int total = 0;
    while(total < len) {
        int r = recv(s, buf + total, len - total, 0);
        if(r <= 0) {
            return 0;
        }
        total += r;
    }

#ifdef _WIN32
    {
        DWORD zero = 0;
        setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&zero, sizeof(zero));
    }
#else
    {
        struct timeval tv0;
        tv0.tv_sec  = 0;
        tv0.tv_usec = 0;
        setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv0, sizeof(tv0));
    }
#endif
    return 1;
}


static void *controlAcceptLoop(void *arg) {
    uint16_t cPort = (uint16_t)(uintptr_t)arg;
    SocketType listener = createListeningSocket(cPort);
    if(listener == INVALID_SOCKET_VAL) {
        fprintf(stderr, "[Server] Failed to listen on controlPort=%u\n", (unsigned)cPort);
        return NULL;
    }
    printf("[Server] Waiting for NAT client on port %u...\n", (unsigned)cPort);

    while(1) {
        struct sockaddr_in caddr;
        socklen_t clen = sizeof(caddr);
        SocketType cs = accept(listener, (struct sockaddr*)&caddr, &clen);
        if(cs == INVALID_SOCKET_VAL) {
            fprintf(stderr, "[Server] accept() error on control channel\n");
            break;
        }
        lock_mutex();
        if(g_natClientConnected) {
            if(g_debug) {
                fprintf(stderr, "[Server] New NAT client, but already occupied.\n");
            }
            const char msg[] = "OCCUP";
            sendEnc(cs, msg, (int)strlen(msg));
            CLOSESOCK(cs);
            unlock_mutex();
            continue;
        }
        unlock_mutex();

        if(g_debug) {
            fprintf(stderr, "[Server] Control channel: accepted, waiting HELLO\n");
        }

        {
            char buf[5];
            memset(buf, 0, sizeof(buf));
            if(!recvAllWithTimeout(cs, buf, 5, 5)) {
                if(g_debug) {
                    fprintf(stderr, "[Server] Did not receive HELLO\n");
                }
                CLOSESOCK(cs);
                continue;
            }
            for(int i=0; i<5; i++){
                buf[i] ^= g_xorKey[i % g_xorKeyLen];
            }
            if(memcmp(buf,"HELLO",5)!=0) {
                if(g_debug) {
                    fprintf(stderr, "[Server] Received not HELLO\n");
                }
                CLOSESOCK(cs);
                continue;
            }
        }
        {
            const char msgOk[]="OK";
            if(!sendEnc(cs, msgOk, 2)) {
                if(g_debug) {
                    fprintf(stderr, "[Server] failed to send OK\n");
                }
                CLOSESOCK(cs);
                continue;
            }
        }
        lock_mutex();
        g_natClientSock = cs;
        g_natClientConnected = 1;
        unlock_mutex();
        printf("[Server] NAT client connected (XOR ok)\n");

        while(1) {
            char c;
            int rr = recv(cs, &c, 1, 0);
            if(rr<=0) {
                if(g_debug) {
                    fprintf(stderr, "[Server] NAT client disconnected\n");
                }
                CLOSESOCK(cs);
                lock_mutex();
                g_natClientSock = INVALID_SOCKET_VAL;
                g_natClientConnected = 0;
                unlock_mutex();
                break;
            }
            /* XOR */
            c ^= g_xorKey[0];
            if(c=='K') {
                if(g_debug) {
                    fprintf(stderr, "[Server] keepalive K\n");
                }
            } else {
                if(g_debug) {
                    fprintf(stderr, "[Server] unknown byte=%d\n",(int)c);
                }
            }
        }
    }
    CLOSESOCK(listener);
    return NULL;
}


static void printUsage(const char* prog) {
    printf("Usage (Server):\n"
           "  %s -c <control_port> -S <socks_port> -x <xor_key> [-u <user> -p <pass>] [-d]\n"
           "Example:\n"
           "  %s -c 9000 -S 1080 -x secret -u test -p 123 -d\n",
           prog, prog);
}

int main(int argc, char* argv[]) {
    if(!initSockets()) {
        return 1;
    }
#ifndef _WIN32
    signal(SIGPIPE, SIG_IGN);
#endif
    init_mutex();

    uint16_t controlPort = 0;
    uint16_t socksPort   = 0;

    for(int i=1; i<argc; i++){
        if(strcmp(argv[i],"-c")==0){
            if(i+1>=argc){ printUsage(argv[0]); goto end; }
            controlPort = (uint16_t)atoi(argv[++i]);
        } else if(strcmp(argv[i],"-S")==0){
            if(i+1>=argc){ printUsage(argv[0]); goto end; }
            socksPort = (uint16_t)atoi(argv[++i]);
        } else if(strcmp(argv[i],"-x")==0){
            if(i+1>=argc){ printUsage(argv[0]); goto end; }
            strncpy(g_xorKey, argv[++i], MAX_STR-1);
            g_xorKeyLen = (int)strlen(g_xorKey);
        } else if(strcmp(argv[i],"-u")==0){
            if(i+1>=argc){ printUsage(argv[0]); goto end; }
            strncpy(g_socksUser, argv[++i], MAX_STR-1);
        } else if(strcmp(argv[i],"-p")==0){
            if(i+1>=argc){ printUsage(argv[0]); goto end; }
            strncpy(g_socksPass, argv[++i], MAX_STR-1);
        } else if(strcmp(argv[i],"-d")==0){
            g_debug=1;
        } else {
            printUsage(argv[0]);
            goto end;
        }
    }
    if(controlPort==0 || socksPort==0 || g_xorKeyLen==0){
        printUsage(argv[0]);
        goto end;
    }

    thread_t tCtl = create_thread(controlAcceptLoop, (void*)(uintptr_t)controlPort);

    SocketType socksListener = createListeningSocket(socksPort);
    if(socksListener == INVALID_SOCKET_VAL){
        fprintf(stderr, "[Server] Unable to listen on socksPort=%u\n",(unsigned)socksPort);
        return 1;
    }
    printf("[Server] SOCKS5 listening on port %u %s. XOR-key size=%d\n",
           (unsigned)socksPort,
           ((strlen(g_socksUser)>0||strlen(g_socksPass)>0)?"(auth=enabled)":"(auth=disabled)"),
           g_xorKeyLen);

    while(1){
        struct sockaddr_in saddr;
        socklen_t slen = sizeof(saddr);
        SocketType c = accept(socksListener, (struct sockaddr*)&saddr, &slen);
        if(c==INVALID_SOCKET_VAL){
            if(g_debug){
                fprintf(stderr,"[Server] accept() error on SOCKS\n");
            }
            break;
        }
        if(g_debug){
            fprintf(stderr,"[Server] New SOCKS client\n");
        }
        thread_t th = create_thread(handleSocksClient, (void*)(uintptr_t)c);
        detach_thread(th);
    }
    CLOSESOCK(socksListener);
    join_thread(tCtl);
end:
    destroy_mutex();
    cleanupSockets();
    return 0;
}

сервер и клиент обратно совместимы с тем, что в статье написано, за исключением команды U для udp associate
 
В целом код на С хорош, грамотно сделаны врапперы recvAll\sendAll, но они используются не везде, где это имеет смысл. Пример:

в server.c в socks5Handshake_SelectMethod
int r = recv(s, (char*)hdr, 2, 0);
лучше использовать recvAll, т.к. криво написанный сокс-клиент может оправлять запрос по байтам тогда recv вычитает 1 байт и вернет единицу и парсер дропнет запрос на строчке
if(r < 2) return 0;
Еще есть враппер recvAllWithTimeout, но опять же он используется не везде, где нужно. Например если клиент подключается к socks-порту и вообще не шлет никакой запрос а тупо держит соединение, то по-факту поток зависнет на первом же вызове recv(), т.е. имеет смысл использовать recvAllWithTimeout при вычитывании запроса от клиента везде.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх