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

How to use fsockopen (or compatible) with SOCKS proxies in PHP?

我使用 fsockopen 和相关函数在 PHP 中编写了一个非邪恶、非垃圾邮件的 IRC 机器人。有用。但是,问题是我需要支持代理(最好是 SOCKS5,但如果更简单的话 HTTP 也可以,我对此表示怀疑)。 fsockopen.

不支持此功能

我已经浏览了 "PHP fsockopen proxy" 和相关查询的所有搜索结果。我知道所有不起作用的东西,所以请不要link其中之一。

fsockopen 的 PHP 手册页提到函数 stream_socket_client()

similar but provides a richer set of options, including non-blocking connection and the ability to provide a stream context.

起初这听起来很有希望,据说允许我用 stream_socket_client 替换 fsockopen 调用并指定代理,也许通过 "stream context"... 但它没有吨。或者是吗?我对 manual.

很困惑

请注意,必须是PHP代码解决方案;我无法支付 "Proxifier" 或使用任何其他外部软件来 "wrap around" 这个。

我尝试过的所有事情似乎总是导致我从服务器得到一堆空输出,然后套接字被强行关闭。请注意,当我使用具有相同网络的 HexChat(普通 IRC 客户端)时,我正在尝试使用的代理可以正常工作,因此不是代理本身有问题。

据我所知,没有为 fsockopenstream_socket_client 设置 SOCKS 或 HTTP 代理的默认选项(我们可以创建上下文并在 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;
}

此函数returns如果连接成功则为文件指针资源,否则为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版本,第二个是认证方法的数量,接下来的字节是认证方法(s) ).
  • 服务器用两个字节响应,即 SOCKS 版本和所选的身份验证方法。
  • 客户端请求连接到目标主机。该请求包含 SOCKS 版本,后跟命令(在本例中为 CONNECT),后跟一个空字节。第四个字节指定地址类型,后面是地址和端口。
  • 服务器最终发送十个字节(或七个或 twenty-two,具体取决于目标地址类型)。第二个字节包含状态,如果请求成功,它应该为零。
  • 服务器代理所有请求。

更多详情:SOCKS Protocol Version 5.

function connect_to_socks5_proxy($host, $port, $destination) {
    $fp = fsockopen($host, $port, $errno, $errstr);
    if ($errno == 0) {
        fwrite($fp, "[=12=]");
        $rsp = fread($fp, 2);
        if ($rsp === "[=12=]" ) {
            list($host, $port) = explode(":", $destination);
            $host = gethostbyname($host); //not required if $host is an IP
            $req = "[=12=]" . inet_pton($host) . pack("n", $port);
            fwrite($fp, $req);
            $rsp = fread($fp, 10);
            if ($rsp[1] === "[=12=]") {
                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 header 添加身份验证。这个header的值是认证类型,后面是用户名和密码,base64编码(基本认证)。

对于 SOCKS5 代理,身份验证过程再次变得更加复杂。看来我们必须将身份验证代码从 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);
}