【问题标题】:cURL as proxy, deal with HTTPS/CONNECT methodcURL 作为代理,处理 HTTPS/CONNECT 方法
【发布时间】:2013-06-05 07:54:35
【问题描述】:

此脚本侦听 IP/端口并打算充当 HTTP(S) 代理。

对 HTTP URL 的请求工作正常,但我在如何处理 HTTPS 请求,更具体地说是客户端向代理发送 CONNECT 请求后的 SSLv3 握手方面遇到了困难。

我最接近的看起来像一个答案是:

  • CURLOPT_HTTPPROXYTUNNEL libcurl 选项用于在客户端和目标服务器之间传输数据
  • stream_socket_enable_crypto() 可能对加密数据进行“处理”

我真的不确定,因此非常感谢您提供有关如何处理此问题的指针。

这是一个示例请求:http://pastebin.com/xkWhGyjW

<?php

class proxy {

    static $server;
    static $client;

    static function headers($str) { // Parses HTTP headers into an array
        $tmp = preg_split("'\r?\n'",$str);
        $output = array();
        $output[] = explode(' ',array_shift($tmp));
        $post = ($output[0][0] == 'POST' ? true : false);

            foreach($tmp as $i => $header) {
                if($post && !trim($header)) {
                    $output['POST'] = $tmp[$i+1];
                    break;
                }
                else {
                    $l = explode(':',$header,2);
                    $output[$l[0]] = $l[0].': '.ltrim($l[1]);
                }
            }
        return $output;
    }

    public function output($curl,$data) {
        socket_write(proxy::$client,$data);
        return strlen($data);
    }
}




$ip = "127.0.0.1";
$port = 50000;

proxy::$server = socket_create(AF_INET,SOCK_STREAM, SOL_TCP);
socket_set_option(proxy::$server,SOL_SOCKET,SO_REUSEADDR,1);
socket_bind(proxy::$server,$ip,50000);
socket_getsockname(proxy::$server,$ip,$port);
socket_listen(proxy::$server);

while(proxy::$client = socket_accept(proxy::$server)) {

    $input = socket_read(proxy::$client,4096);
    preg_match("'^([^\s]+)\s([^\s]+)\s([^\r\n]+)'ims",$input,$request);
    $headers = proxy::headers($input);

        echo $input,"\n\n";
            if(preg_match("'^CONNECT '",$input)) { // HTTPS
                // Tell the client we can deal with this
                socket_write(proxy::$client,"HTTP/1.1 200 Connection Established\r\n\r\n");
                // Client sends binary data here (SSLv3, TLS handshake, Client hello?)
                // socket_read(proxy::$client,4096);
                // ?
            }
            else { // HTTP

                        $input = preg_replace("'^([^\s]+)\s([a-z]+://)?[a-z0-9\.\-]+'","\\1 ",$input);
                        $curl = curl_init($request[2]);
                        curl_setopt($curl,CURLOPT_HEADER,1);
                        curl_setopt($curl,CURLOPT_HTTPHEADER,$headers);
                        curl_setopt($curl,CURLOPT_TIMEOUT,15);
                        curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
                        curl_setopt($curl,CURLOPT_NOPROGRESS,1);
                        curl_setopt($curl,CURLOPT_VERBOSE,1);
                        curl_setopt($curl,CURLOPT_AUTOREFERER,true);
                        curl_setopt($curl,CURLOPT_FOLLOWLOCATION,1);
                        curl_setopt($curl,CURLOPT_WRITEFUNCTION, array("proxy","output"));
                        curl_exec($curl);
                        curl_close($curl);
            }
    socket_close(proxy::$client);
}
socket_close(proxy::$server);


?>

【问题讨论】:

  • 您能在 pastebin 上发布您的 HTTP URL 请求代码吗?
  • 当然...我正在使用 cURL 命令行进行测试。 pastebin.com/xkWhGyjW
  • 您是否尝试过 google.com 以外的其他网站(根据您的 pastebin)? (见这里superuser.com/questions/404116/…)您是否尝试过在没有代理的情况下进行连接,即curl -L -i -v "https://www.google.com/"?如果在使用代理和直接连接时将 --trace tracetxt.tmp 添加到 curl 调用中,输出是什么?也许痕迹的差异可能会有所帮助?
  • 好的,谢谢。我试过 --trace 来了解正在发送的数据,但没有给我更多关于如何处理连接之间的数据的指导。

标签: php sockets curl ssl proxy


【解决方案1】:

如果我理解正确,您正在用 PHP 编写 HTTP 代理服务器。当您想使用 PHP cURL 库连接到代理服务器并使用 CONNECT 而不是 GET 时,使用 CURLOPT_HTTPPROXYTUNNEL 选项。在这种情况下,它不相关。

当您的代理服务器 (PROXY) 收到 CONNECT 请求时,它应该使用 socket_createsocket_connect 连接到指定的主机 (ENDPOINT)。建立连接后,通过发送HTTP/1.1 200 Connection Established 让客户端(CLIENT)知道。之后,您需要将 ENDPOINT 发送到 PROXY 的所有数据复制到 CLIENT,并将 CLIENT 发送到 PROXY 的所有数据复制到 ENDPOINT。

在您的示例中使用 cURL 将创建多个连接。为了处理多个连接,我使用了pcntl_fork,它在每个CONNECT 请求上派生一个新进程。

这是一个工作示例:

<?php

class proxy {

    static $server;
    static $client;

    static function headers($str) { // Parses HTTP headers into an array
        $tmp = preg_split("'\r?\n'",$str);
        $output = array();
        $output[] = explode(' ',array_shift($tmp));
        $post = ($output[0][0] == 'POST' ? true : false);

            foreach($tmp as $i => $header) {
                if($post && !trim($header)) {
                    $output['POST'] = $tmp[$i+1];
                    break;
                }
                else {
                    $l = explode(':',$header,2);
                    $output[$l[0]] = $l[0].': '.ltrim($l[1]);
                }
            }
        return $output;
    }

    public function output($curl,$data) {
        socket_write(proxy::$client,$data);
        return strlen($data);
    }
}




$ip = "127.0.0.1";
$port = 50000;

proxy::$server = socket_create(AF_INET,SOCK_STREAM, SOL_TCP);
socket_set_option(proxy::$server,SOL_SOCKET,SO_REUSEADDR,1);
socket_bind(proxy::$server,$ip,50000);
socket_getsockname(proxy::$server,$ip,$port);
socket_listen(proxy::$server);

while(proxy::$client = socket_accept(proxy::$server)) {

    $input = socket_read(proxy::$client,4096);
    preg_match("'^([^\s]+)\s([^\s]+)\s([^\r\n]+)'ims",$input,$request);
    $headers = proxy::headers($input);

        echo $input,"\n\n";
            if(preg_match("'^CONNECT ([^ ]+):(\d+) '",$input,$match)) { // HTTPS
                // fork to allow multiple connections
                if(pcntl_fork())
                    continue;

                $connect_host = $match[1];
                $connect_port = $match[2];

                // connect to endpoint
                $connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
                if(!socket_connect($connection, gethostbyname($connect_host), $connect_port))
                    exit;

                // let the client know that we're connected
                socket_write(proxy::$client,"HTTP/1.1 200 Connection Established\r\n\r\n");

                // proxy data
                $all_sockets = array($connection, proxy::$client);
                $null = null;
                while(($sockets = $all_sockets)
                      && false !== socket_select($sockets, $null, $null, 10)
                ) {
                    // can we read from the client without blocking?
                    if(in_array(proxy::$client, $sockets)) {
                        $buf = null;
                        socket_recv(proxy::$client, $buf, 8192, MSG_DONTWAIT);
                        echo "CLIENT => ENDPOINT (" . strlen($buf) . " bytes)\n";
                        if($buf === null)
                            exit;
                        socket_send($connection, $buf, strlen($buf), 0);
                    }

                    // can we read from the endpoint without blocking?
                    if(in_array($connection, $sockets)) {
                        $buf = null;
                        socket_recv($connection, $buf, 8192, MSG_DONTWAIT);
                        echo "ENDPOINT => CLIENT (" . strlen($buf) . " bytes)\n";
                        if($buf === null)
                            exit;
                        socket_send(proxy::$client, $buf, strlen($buf), 0);
                    }
                }

                exit;
            }
            else { // HTTP

                        $input = preg_replace("'^([^\s]+)\s([a-z]+://)?[a-z0-9\.\-]+'","\\1 ",$input);
                        $curl = curl_init($request[2]);
                        curl_setopt($curl,CURLOPT_HEADER,1);
                        curl_setopt($curl,CURLOPT_HTTPHEADER,$headers);
                        curl_setopt($curl,CURLOPT_TIMEOUT,15);
                        curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
                        curl_setopt($curl,CURLOPT_NOPROGRESS,1);
                        curl_setopt($curl,CURLOPT_VERBOSE,1);
                        curl_setopt($curl,CURLOPT_AUTOREFERER,true);
                        curl_setopt($curl,CURLOPT_FOLLOWLOCATION,1);
                        curl_setopt($curl,CURLOPT_WRITEFUNCTION, array("proxy","output"));
                        curl_exec($curl);
                        curl_close($curl);
            }
    socket_close(proxy::$client);
}
socket_close(proxy::$server);

【讨论】:

  • 谢谢,我今天晚些时候试试这个。我已经分叉了,但只是将其删除以在此处发布,因为主要关注的是处理充当代理 / 的 HTTPS 连接和 CONNECT 方法。请注意,这样的分叉会留下僵尸进程,使用类似 pcntl_signal(SIGCHLD, SIG_IGN) 的东西很有用......并且客户端/服务器套接字需要由父级和子级关闭。
  • 是的,您必须改进代码以确保安全。它更像是所需功能的一个简单粗暴的示例。
  • 我可以确认这也有效。我很惊讶在 Packagist 上找到一个有效的 PHP HTTPS 代理实现是多么困难。我会用这个开始,但是有测试和 GitHub 星星的东西会很让人放心。不过,感谢 Albert 所做的工作。
  • 但是,proxy::headers 中的 HTTP 代理有一个 bug,代码在 PHP 7.1 下会发出很多警告。我已经整理好了,以后会在 GitHub 上发布它。
猜你喜欢
  • 2017-08-17
  • 2010-11-21
  • 2021-02-21
  • 2019-01-05
  • 2016-07-06
  • 2015-02-05
  • 2019-04-02
  • 2019-04-28
  • 2013-06-04
相关资源
最近更新 更多