如何在PHP中与SOCKS代理一起使用fsockopen(或兼容)?

c2e8gylq  于 2023-11-16  发布在  PHP
关注(0)|答案(2)|浏览(78)

我用PHP编写了一个非恶意的、非垃圾邮件的IRC bot,使用了fsockopen和相关函数。它可以工作。然而,问题是我需要支持代理(最好是SOCKS5,但如果HTTP更容易的话也可以,我怀疑)。fsockopen不支持这一点。
我已经浏览了所有关于“PHP fsockopen proxy”的搜索结果和相关查询。我知道所有不起作用的东西,所以请不要链接到其中任何一个。
fsockopen的PHP手册页提到了函数stream_socket_client()
类似,但提供了更丰富的选项集,包括非阻塞连接和提供流上下文的能力。
一开始这听起来很有希望,应该允许我用stream_socket_client替换fsockopen调用并指定一个代理,也许是通过一个“流上下文”.
请注意,它必须是一个PHP代码的解决方案,我不能支付“代理”或使用任何其他外部软件“ Package ”这一点。
我尝试的所有方法似乎总是导致我从服务器得到一堆空输出,然后套接字被强制关闭。请注意,当我使用HexChat(一个普通的IRC客户端)时,我正在尝试的代理工作,在相同的网络中,所以这不是代理本身的错误。

xlpyo6sf

xlpyo6sf1#

据我所知,fsockopenstream_socket_client没有默认选项来设置SOCKS或HTTP代理(我们可以在HTTP选项中创建上下文并设置代理,但这不适用于stream_socket_client)。然而,我们可以手动建立连接。
连接到HTTP代理非常简单:

  • 客户端连接到代理服务器并提交一个PROCEECT请求。
  • 如果请求被接受,则服务器响应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;
}

字符串
这个函数返回一个文件指针资源,如果连接成功,否则返回一个文件指针资源。我们可以使用该资源与目标主机通信。

$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);
}


SOCKS 5代理的通信协议稍微复杂一些:

  • 客户端连接到代理服务器并发送(至少)三个字节:第一个字节是SOCKS版本,第二个字节是身份验证方法的数量,接下来的字节是身份验证方法。
  • 服务器用两个字节、SOCKS版本和所选的身份验证方法进行响应。
  • 客户端请求连接到目的主机。请求包含SOCKS版本,后跟命令(本例中为SOCKECT),后跟一个空字节。第四个字节指定地址类型,后跟地址和端口。
  • 服务器最后发送10个字节(或7个或22个,取决于目的地址类型)。第二个字节包含状态,如果请求成功,它应该是0。
  • 服务器代理所有请求。

<!- -!>
更多详情:SOCKS Protocol Version 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://或tls://协议与fsockopen创建SSL连接,因为这将尝试与代理服务器(而不是目标主机)创建SSL连接。但是,在与代理服务器建立连接后,可以使用stream_socket_enable_crypto启用SSL并与目标主机创建安全通信通道。这需要禁用对等验证,可以使用自定义上下文使用stream_socket_client完成此操作。请注意,禁用对等验证可能会导致安全问题。
对于HTTP代理,我们可以使用Proxy-Authenticate头添加身份验证。此头的值是身份验证类型,后跟用户名和密码,base64编码(基本身份验证)。
对于SOCKS 5代理,身份验证过程再次变得更加复杂。似乎我们必须将身份验证代码从0x00(不需要身份验证)更改为0x02(用户名/密码身份验证)。我不清楚如何使用身份验证值创建请求,因此无法提供示例。

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);
}

wztqucjr

wztqucjr2#

下面是使用USERNAME/PASSWORD身份验证的SOCKS 5代理的示例
如果你想使用SOCKS 5进行身份验证,有两个文档可以帮助你:SOCKS5 ProtocolUsername/Password Authentication for SOCKS
与之前示例的主要区别是在第一次请求期间使用方法X '02'而不是X '00',并在第一次请求之后发送用户名和密码。

function connectToSocks5Proxy($sock5ProxyHost, $sock5ProxyPort, $destinationIp, $destinationPort, $username, $password)
{
    $fsock = fsockopen($sock5ProxyHost, $sock5ProxyPort, $errno, $errstr);

    if ($errno !== 0) {
        echo "Connection failed, $errno, $errstr\n";

        return false;
    }

    // +----+----------+----------+
    // |VER | NMETHODS | METHODS  |
    // +----+----------+----------+
    // | 1  |    1     | 1 to 255 |
    // +----+----------+----------+
    $request = "\5\1\2";
    fwrite($fsock, $request); // \5 - socks version = 5, \1 - number of method identifier octets, \2 - USERNAME/PASSWORD authentication

    $response = fread($fsock, 2);

    if ($response !== "\5\2") { // \5 - socks version = 5, \2 - selected METHOD in our case is \2 - USERNAME/PASSWORD authentication
        echo "Unsupported protocol or unsupported method\n";

        return false;
    }

    // +----+------+----------+------+----------+
    // |VER | ULEN |  UNAME   | PLEN |  PASSWD  |
    // +----+------+----------+------+----------+
    // | 1  |  1   | 1 to 255 |  1   | 1 to 255 |
    // +----+------+----------+------+----------+
    $request = "\1" . chr(strlen($username)) . $username . chr(strlen($password)) . $password;
    fwrite($fsock, $request);

    $response = fread($fsock, 2);

    if ($response !== "\1\0") { // \1 - version of the subnegotiation, \0 - status. \0 indicates success otherwise failure
        echo "Unsupported protocol or unsupported method\n";

        return false;
    }

    $ipAddress = inet_pton($destinationIp);

    //  +----+-----+-------+------+----------+----------+
    //  |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
    //  +----+-----+-------+------+----------+----------+
    //  | 1  |  1  | X'00' |  1   | Variable |    2     |
    //  +----+-----+-------+------+----------+----------+
    $request = "\5\1\0\1" . $ipAddress . pack("n", $destinationPort); // Here is a tricky moment. \1 indecates to IP V4. If you want to use domain name or IP V6 you should use \3 or \4 respectively
    fwrite($fsock, $request);

    $connectResponseLength = strlen($ipAddress) + 6; // strlen($ipAddress) + 1 byte VER + 1 byte CMD + 1 byte RSV + 1 byte ATYP + 2 bytes DST.PORT
    $response = fread($fsock, $connectResponseLength);

    if (substr($response, 0, 2) !== "\5\0") {
        echo "Request denied, status: " . ord($response[1]) . "\n";

        return false;
    }

    return $fsock;
}

字符串

相关问题