Как использовать fsockopen (или совместимый) с прокси SOCKS в PHP? - PullRequest
0 голосов
/ 25 февраля 2019

Я закодировал IRC-бот без зла и спама в PHP, используя fsockopen и связанные с ним функции.Оно работает.Однако проблема в том, что мне нужно поддерживать прокси-серверы (желательно SOCKS5, но HTTP тоже хорошо, если это проще, в чем я сомневаюсь).Это не поддерживается fsockopen.

. Я просмотрел все результаты поиска по запросу "PHP fsockopen proxy" и связанных с ним запросов.Я знаю обо всех вещах, которые не работают, поэтому, пожалуйста, не связывайтесь ни с одним из них.

Страница руководства PHP по fsockopen упоминает функцию stream_socket_client() как

аналогично, но предоставляет более богатый набор опций, включая неблокирующее соединение и возможность предоставлять контекст потока.

Поначалу это звучало многообещающе, якобы позволяя мне просто заменить вызов fsockopenс stream_socket_client и указать прокси, возможно, через «потоковый контекст» ... но это не так.Или это?Меня очень смущает руководство .

Обратите внимание, что это должно быть решение для PHP-кода;Я не могу заплатить за «Proxifier» или использовать любое другое внешнее программное обеспечение, чтобы «обернуть» это.

Все, что я пробовал, похоже, всегда приводит к тому, что я получаю кучу пустых выводов с сервера, изатем розетка принудительно закрывается.Обратите внимание, что прокси-сервер, с которым я пытаюсь работать, работает, когда я использую HexChat (обычный IRC-клиент) в той же сети, поэтому виноваты не сами прокси.

1 Ответ

0 голосов
/ 05 марта 2019

Насколько я знаю, по умолчанию нет опции для установки SOCKS или HTTP-прокси для fsockopen или stream_socket_client (мы могли бы создать контекст и установить прокси в параметрах HTTP, но это не относится к stream_socket_client).Однако мы можем установить соединение вручную.

Подключение к прокси HTTP довольно просто:

  • Клиент подключается к прокси-серверу и отправляет запрос CONNECT.
  • Сервер отвечает 200, если запрос принят.
  • Затем сервер передает все запросы между клиентом и хостом назначения.

function connect_to_http_proxy($host, $port, $destination) {
    $fp = fsockopen($host, $port, $errno, $errstr);
    if ($errno == 0) {
        $connect = "CONNECT $destination HTTP/1.1\r\n\r\n";
        fwrite($fp, $connect);
        $rsp = fread($fp, 1024);
        if (preg_match('/^HTTP\/\d\.\d 200/', $rsp) == 1) {
            return $fp;
        }
        echo "Request denied, $rsp\n";
        return false;
    }
    echo "Connection failed, $errno, $errstr\n";
    return false;
}

Эта функция возвращает ресурс указателя файла, если соединение установлено успешно, иначе FALSE.Мы можем использовать этот ресурс для связи с хостом назначения.

$proxy = "138.204.48.233";
$port = 8080;
$destination = "api.ipify.org:80";
$fp = connect_to_http_proxy($proxy, $port, $destination);
if ($fp) {
    fwrite($fp, "GET /?format=json HTTP/1.1\r\nHost: $destination\r\n\r\n");
    echo fread($fp, 1024);
    fclose($fp);
}

Протокол связи для прокси-серверов SOCKS5 немного сложнее:

  • Клиент подключается к прокси-серверу и отправляет (как минимум) три байта:первый байт - это версия SOCKS, второй - количество методов аутентификации, следующий байт - это метод (ы) аутентификации.
  • Сервер отвечает двумя байтами, версией SOCKS и выбранным методом аутентификации.
  • Клиент запрашивает соединение с хостом назначения.Запрос содержит версию SOCKS, за которой следует команда (в данном случае CONNECT), за которой следует нулевой байт.Четвертый байт указывает тип адреса, за ним следуют адрес и порт.
  • Сервер наконец отправляет десять байтов (или семь или двадцать два, в зависимости от типа адреса назначения).Второй байт содержит статус, и он должен быть нулевым, если запрос успешен.
  • Сервер проксирует все запросы.

Подробнее: Версия протокола SOCKS 5 .

function connect_to_socks5_proxy($host, $port, $destination) {
    $fp = fsockopen($host, $port, $errno, $errstr);
    if ($errno == 0) {
        fwrite($fp, "\05\01\00");
        $rsp = fread($fp, 2);
        if ($rsp === "\05\00" ) {
            list($host, $port) = explode(":", $destination);
            $host = gethostbyname($host); //not required if $host is an IP
            $req = "\05\01\00\01" . inet_pton($host) . pack("n", $port);
            fwrite($fp, $req);
            $rsp = fread($fp, 10);
            if ($rsp[1] === "\00") {
                return $fp;
            }
            echo "Request denied, status: " . ord($rsp[1]) . "\n";
            return false;
        } 
        echo "Request denied\n";
        return false;
    }
    echo "Connection failed, $errno, $errstr\n";
    return false;
}

Эта функция работает так же, как connect_to_http_proxy.Хотя обе функции протестированы, лучше всего использовать библиотеку;код предоставляется в основном для образовательных целей.


Поддержка SSL и аутентификация.

Мы не можем создать SSL-соединение с fsockopen, используя протокол ssl: // или tls: //, потому что это будет пытаться создать SSL-соединение с прокси-сервером, а не с целевым хостом.Но возможно включить SSL с stream_socket_enable_crypto и создать безопасный канал связи с адресатом после установления соединения с прокси-сервером.Для этого необходимо отключить одноранговую проверку, что можно сделать с помощью stream_socket_client в пользовательском контексте.Обратите внимание, что отключение одноранговой проверки может быть проблемой безопасности.

Для HTTP-прокси мы можем добавить аутентификацию с заголовком Proxy-Authenticate.Значением этого заголовка является тип аутентификации, за которым следуют имя пользователя и пароль в кодировке base64 (базовая аутентификация).

Для прокси-серверов SOCKS5 процесс аутентификации - опять же - более сложный.Кажется, нам нужно изменить код аутентификации fron 0x00 (АУТЕНТИФИКАЦИЯ НЕ ТРЕБУЕТСЯ) на 0x02 (аутентификация USERNAME / PASSWORD).Мне не понятно, как создать запрос со значениями аутентификации, поэтому я не могу привести пример.

function connect_to_http_proxy($host, $port, $destination, $creds=null) {
    $context = stream_context_create(
        ['ssl'=> ['verify_peer'=> false, 'verify_peer_name'=> false]]
    );
    $soc = stream_socket_client(
        "tcp://$host:$port", $errno, $errstr, 20, 
        STREAM_CLIENT_CONNECT, $context
    );
    if ($errno == 0) {
        $auth = $creds ? "Proxy-Authorization: Basic ".base64_encode($creds)."\r\n": "";
        $connect = "CONNECT $destination HTTP/1.1\r\n$auth\r\n";
        fwrite($soc, $connect);
        $rsp = fread($soc, 1024);
        if (preg_match('/^HTTP\/\d\.\d 200/', $rsp) == 1) {
            return $soc;
        }
        echo "Request denied, $rsp\n";
        return false;
    }
    echo "Connection failed, $errno, $errstr\n";
    return false;
}

$host = "proxy IP";
$port = "proxy port";
$destination = "chat.freenode.net:6697";
$credentials = "user:pass";
$soc = connect_to_http_proxy($host, $port, $destination, $credentials);
if ($soc) {
    stream_socket_enable_crypto($soc, true, STREAM_CRYPTO_METHOD_ANY_CLIENT);
    fwrite($soc,"USER test\nNICK test\n");
    echo fread($soc, 1024);
    fclose($soc);
}
...