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

时间:2019-02-25 10:04:39

标签: php windows proxy irc fsockopen

我已经使用fsockopen和相关功能用PHP编写了一个非恶意,非垃圾邮件的IRC机器人。有用。但是,问题是我需要支持代理(最好是SOCKS5,但是如果以某种方式更简单的话,HTTP也可以,我对此表示怀疑)。 fsockopen不支持此功能。

我已经遍历了所有“ PHP fsockopen代理”和相关查询的搜索结果。我知道所有不起作用的东西,所以请不要链接到其中之一。

fsockopen的PHP手册页将功能stream_socket_client()提到为

  

相似,但是提供了更丰富的选项集,包括非阻塞连接和提供流上下文的功能。

起初听起来很有希望,可能是让我可以将fsockopen替换为stream_socket_client并指定代理(也许通过“流上下文”),但事实并非如此。还是呢?我对manual感到非常困惑。

请注意,它必须是PHP代码解决方案;我无法支付“ Proxifier”的费用,也无法使用任何其他外部软件来“包装”此物品。

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

1 个答案:

答案 0 :(得分:6)

据我所知,没有为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;
}

如果连接成功,此函数返回文件指针资源,否则为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),后跟一个空字节。第四个字节指定地址类型,后跟地址和端口。
  • 服务器最终发送10个字节(或7或22个字节,具体取决于目标地址类型)。第二个字节包含状态,如果请求成功,则应为零。
  • 服务器代理所有请求。

更多详细信息: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编码的用户名和密码(基本身份验证)。

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