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

1 — прокси-сервер Socks 4/5

прежде чем мы представим простые методы проверки того, работает ли IP-адрес с прокси-сервером sock4/5, нам нужно понять механизм связи между клиентом и сервером в протоколе sock.

Как правило, в большинстве случаев использования протокола sock механизм шлюза IPv6/IPv4 на основе SOCKS основан на механизме, который ретранслирует два «завершенных» соединения IPv4 и IPv6 на «прикладном уровне» (сервере SOCKS).

Схема механизма SOCKS:

                  basic SOCKS-based gateway mechanism.

                  Client C       Gateway G     Destination D
               +-----------+     (Server)
               |Application|
           +-->+===========+  +-------------+  +-----------+
      same-+   |*SOCKS Lib*|  |  *Gateway*  |  |Application|
       API +-->+===========+  +=====---=====+  +-----------+
               | Socket DNS|  | Socket  DNS |  | Socket DNS|
               +-----------+  +-------------+  +-----------+
               | [ IPv X ] |  |[IPvX]|(IPvY)|  | ( IPv Y ) |
               +-----------+  +-------------+  +-----------+
               |Network I/F|  | Network I/F |  |Network I/F|
               +-----+-----+  +---+-----+---+  +-----+-----+
                     |            |     |            |
                     +============+     +------------+
                       socksified           normal
                       connection         connection
                      (ctrl)+data          data only

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

Для этого нам нужно знать, какие команды разрешены в каждом протоколе Sock и как мы можем их использовать?

Мы можем обобщить список команд следующим образом:

Протокол SOCKSv5 состоит из трех команд (CONNECT, BIND и UDP ASSOCIATE). Все три команды могут применяться в механизме шлюза IPv6/IPv4 на основе SOCKS.

В этой статье предполагается использование в основном команды CONNECT, поскольку команда CONNECT является основной и наиболее часто используемой командой в механизме SOCKS. Так как команда CONNECT не имеет четких слабых мест, мы можем использовать ее свободно без каких-либо соображений. Другие команды (BIND и UDP ASSOCIATE) имеют следующие недостатки. Таким образом, мы должны учитывать эти моменты, когда используем в механизме команды BIND или UDP ASSOCIATE. Команда BIND в основном предназначена для поддержки рандеву по обратному каналу для приложений типа FTP. Таким образом, общее использование команды BIND может вызвать проблемы. Команда UDP ASSOCIATE в основном предназначена для простых приложений UDP (например, archie). Он недостаточно общий для поддержки большого класса приложений, использующих как TCP, так и UDP.

Теперь мы знаем, что все, что нам нужно, это проверить, есть ли у IP-адреса открытый порт, на который мы собираемся отправить тест команды Connect с различными значениями буфера в зависимости от версии протокола Sock (4, 5).

Во-первых, давайте перейдем к структуре буфера пакетов команды подключения в обеих версиях sock:

The SOCKS request is formed as follows:

        +----+-----+-------+------+----------+----------+
        |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
        +----+-----+-------+------+----------+----------+
        | 1  |  1  | X'00' |  1   | Variable |    2     |
        +----+-----+-------+------+----------+----------+

     Where:

          o  VER    protocol version: X'05' or X'04'
          o  CMD
             o  CONNECT X'01'
             o  BIND X'02'
             o  UDP ASSOCIATE X'03'
          o  RSV    RESERVED
          o  ATYP   address type of following address
             o  IP V4 address: X'01'
             o  DOMAINNAME: X'03'
             o  IP V6 address: X'04'
          o  DST.ADDR       desired destination address
          o  DST.PORT desired destination port in network octet
             order

   The SOCKS server will typically evaluate the request based on source
   and destination addresses, and return one or more reply messages, as
   appropriate for the request type.

Теперь у нас есть все необходимое для создания реального примера кода на Rust:

К настоящему времени я должен упомянуть, что в зависимости от того, что мы сеем, мы можем проверить, даже если сервер защищен механизмом аутентификации или нет, используя следующие значения методов:

The values currently defined for METHOD are:

          o  X'00' NO AUTHENTICATION REQUIRED
          o  X'01' GSSAPI
          o  X'02' USERNAME/PASSWORD
          o  X'03' to X'7F' IANA ASSIGNED
          o  X'80' to X'FE' RESERVED FOR PRIVATE METHODS
          o  X'FF' NO ACCEPTABLE METHODS

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

    let mut res = (false, Proto::SOCKS5);
            for _ in 0..retrys {
                let addrs = format!("{}:{}", proxy.host.as_str(), proxy.port);
                if let Ok(Ok(socket)) =
                    future::timeout(dur, TcpStream::connect(addrs.clone())).await
                {
                    let packet_len = 3;
                    let packet = [
                        5, // protocol version (value 4 is for socks4)
                        1, // method count
                        0, // method
                        0, // no auth (always offered)
                    ];
                    if let Ok(_) = future::timeout(Duration::from_millis(900), async {
                        let _ = socket.writable().await;
                        socket.try_write(&packet[..packet_len])
                    })
                    .await
                    {
                        let mut buf = [0; 2];
                        if let Ok(Ok(_)) = future::timeout(Duration::from_millis(900), async {
                            let _ = socket.readable().await;
                            let _ = socket.set_ttl(255); // linux
                            socket.try_read(&mut buf)
                        })
                        .await
                        {
                            let response_version = buf[0];
                            if response_version == 5 {
                                res = (true, Proto::SOCKS5);
                                break;
                            }
                        }
                    };
                }
            }
     return res;

Дополнительные сведения см. в репозитории: https://github.com/KM8Oz/open_proxies/blob/main/src/lib.rs#L62.

2 — Http/Https прокси-сервер

Обзор того, как работает прокси-сервер HTTP, можно описать следующими двумя типами:

  • Переадресация прокси

Прямой прокси, или шлюз, или просто «прокси» предоставляет услуги прокси клиенту или группе клиентов. Вероятно, в Интернете существуют сотни тысяч открытых прокси-серверов. Они хранят и пересылают интернет-сервисы (такие как DNS или веб-страницы), чтобы уменьшить и контролировать пропускную способность, используемую группой.

Прямые прокси-серверы также могут быть анонимными прокси-серверами и позволять пользователям скрывать свой IP-адрес при просмотре веб-страниц или использовании других интернет-сервисов. TOR (The Onion Router) направляет интернет-трафик через несколько прокси для обеспечения анонимности.

  • Обратные прокси

Как следует из названия, обратный прокси делает противоположное тому, что делает прямой прокси: прямой прокси действует от имени клиентов (или запрашивающих хостов). Прямые прокси могут скрывать личности клиентов, тогда как обратные прокси могут скрывать личности серверов. Обратные прокси имеют несколько вариантов использования, вот некоторые из них:

Балансировка нагрузки: распределите нагрузку на несколько веб-серверов,

Кэшировать статический контент: разгружайте веб-серверы, кэшируя статический контент, такой как изображения,

Сжатие: сжимайте и оптимизируйте содержимое для ускорения загрузки.

Нам не нужно вдаваться в подробности того, как работает протокол httpv1/2, и мы должны перейти непосредственно к тому, чтобы увидеть, как мы можем использовать простую команду CONNECT (CONNECT — метод hop-by-hop в протоколе HTTP) для проверки открытый порт прокси-сервера, чтобы увидеть, работает ли он и что, является ли TLS (SSL) обязательным или нет.

Для нас в этой статье важнее всего знать, что метод HTTP CONNECT запускает двустороннюю связь с запрошенным ресурсом. Его можно использовать для открытия туннеля. или просто получить один из следующих статусов ответов:

- 200 OK connection can be established 
- 511 Network Authentication Required
- 401 Unauthorized
....

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

Например, метод CONNECT можно использовать для доступа к веб-сайтам, использующим SSL (HTTPS). Клиент запрашивает HTTP прокси-сервер для туннелирования TCP соединения с желаемым пунктом назначения. Затем сервер переходит к установлению соединения от имени клиента. Как только соединение установлено сервером, Прокси-сервер продолжает проксировать поток TCP к клиенту и от него.

Простой пример в Rust может быть следующим:

  • для HTTPS:
 let mut res = (false, Proto::HTTPS);
            let connector = async_tls::TlsConnector::default();
            let addrs = format!("{}:{}", proxy.host.as_str(), proxy.port);
            if let Ok(Ok(socket)) =
                future::timeout(dur, async_std::net::TcpStream::connect(addrs.clone())).await
            {
                let _connector = connector.clone();
                if let Ok(Ok(mut stream_socket)) =
                    future::timeout(dur, _connector.connect(proxy.host.as_str(), socket)).await
                {
                    let hello = format!(
                        "CONNECT {0}:{1} HTTP/1.1\r\n\
                             Host: {0}:{1}\r\n\
                             Proxy-Connection: Keep-Alive\r\n",
                        proxy.host.as_str(),
                        proxy.port
                    );
                    let request = hello.as_bytes();
                    if let Ok(_) = future::timeout(Duration::from_millis(900), async {
                        stream_socket.write_all(&request.clone())
                    })
                    .await
                    {
                        let mut buf = [0; 4096];
                        if let Ok(_) = future::timeout(Duration::from_millis(900), async {
                            stream_socket.read(&mut buf)
                        })
                        .await
                        {
                            const MAXIMUM_RESPONSE_HEADERS: usize = 16;
                            let mut response_headers = [EMPTY_HEADER; MAXIMUM_RESPONSE_HEADERS];
                            let mut response = Response::new(&mut response_headers[..]);
                            if let Ok(_) = response.parse(&buf) {
                                if response.code == Some(200) {
                                    res = (true, Proto::HTTP);
                                }
                            }
                        }
                    }
                }
            }
     return res;
  • для HTTP:
 let mut res = (false, Proto::HTTP);
            for _ in 0..retrys {
                let addrs = format!("{}:{}", proxy.host.as_str(), proxy.port);
                if let Ok(Ok(mut socket)) =
                    future::timeout(dur, async { std::net::TcpStream::connect(addrs.clone()) })
                        .await
                {
                    let hello = format!(
                        "CONNECT {0}:{1} HTTP/1.1\r\n\
                         Host: {0}:{1}\r\n\
                         Proxy-Connection: Keep-Alive\r\n",
                        proxy.host.as_str(),
                        proxy.port
                    );
                    let request = hello.as_bytes();
                    if let Ok(_) = future::timeout(Duration::from_millis(900), async {
                        socket.write_all(&request.clone())
                    })
                    .await
                    {
                        let mut buf = [0; 4096];
                        if let Ok(_) = future::timeout(Duration::from_millis(900), async {
                            let _ = socket.set_ttl(255);
                            socket.read(&mut buf)
                        })
                        .await
                        {
                            const MAXIMUM_RESPONSE_HEADERS: usize = 16;
                            let mut response_headers = [EMPTY_HEADER; MAXIMUM_RESPONSE_HEADERS];
                            let mut response = Response::new(&mut response_headers[..]);
                            if let Ok(_) = response.parse(&buf) {
                                if response.code == Some(200) {
                                    res = (true, Proto::HTTP);
                                    break;
                                }
                            }
                        }
                    };
                }
            }
     return res;

В заключение я призываю любого читателя попробовать и проверить мой репозиторий на [https://github.com/KM8Oz/open_proxys], где я собрал приложение командной строки для всего того, что мы обсуждали с параллелизмом и многопоточный формат для более быстрой проверки больших файлов списка прокси.

Спасибо за прочтение

связаться со мной в KMOZ.DEV.

Использованная литература: