Однажды мне понадобился прокси-сервер, работающий не как демон/служба а в контексте процесса веб-сервера. Подходящего не нашел поэтому написал свой, очень простой и достаточно юзабельный. Описание Состоит из двух частей. На удаленный веб-сервер заливается первая часть прокси, написанная на PHP - скрипт pproxy.php. На локалхосте запускается вторая часть прокси, реализованная на Perl (скрипт plocal.pl), которая прослушивает порт как HTTP-прокси. На этот локальный HTTP прокси настраивается, например, браузер. Скрипты pproxy.php - первая, удаленная часть прокси, на PHP: PHP: <?php //$secret = 'pproxypass'; if(isset($_POST['query']) && isset($_POST['host'])) { if(isset($secret) && ($_POST['secret'] != $secret))exit; header('Content-type: application/octet-stream'); @set_time_limit(0); $query = base64_decode(str_replace(' ', '+', $_POST['query'])); @list($host, $port) = @explode(':', base64_decode($_POST['host'])); if(!$port)$port = 80; $ip = gethostbyname($host); if($fp = @fsockopen($ip, $port, $errno, $errstr, 20)) { fwrite($fp, $query); while(!feof($fp)) { $answer = fread($fp, 1024); echo $answer; } fclose($fp); } exit; } ?> plocal.pl - вторая, локальная часть прокси, на Perl: Code: use HTTP::Daemon; use MIME::Base64 (); use Getopt::Long; use POSIX ":sys_wait_h"; use strict; my $user_agent = 'Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4'; my $errheader = "HTTP/1.1 200 OK\x0D\x0AContent-Type: text/plain\x0D\x0A\x0D\x0A"; my $errmsg1 = $errheader . 'Could not connect to server'; my $errmsg2 = $errheader . 'Could not connect to pproxy'; my ($pproxyhost, $pproxyport, $pproxyurl); my ($tunnelhost, $tunnelport); my ($pproxy, $bindport, $tunnel, $secret); my ($destaddr, $destport, $desturl); #Вывод справки Usage() if @ARGV==0; #Задание опций GetOptions( "px=s" => \$pproxy, "bp=s" => \$bindport, "tpx=s" => \$tunnel, "pwd=s" => \$secret ); die "need pproxy.php url" unless $pproxy; #Получение параметров $pproxy =~ /http:\/\/([\w\.\-]+)(:\d*)?\/(.+)/; $pproxyhost = $1; $pproxyport = substr($2, 1); $pproxyurl = '/' . $3; defined($pproxyport) || ($pproxyport = 80); defined($bindport) || ($bindport = 8008); if(defined($tunnel)) { $tunnel =~ /http:\/\/([\w\.\-]+):(\d*)?/; $tunnelhost = $1; $tunnelport = $2; $destaddr = $tunnelhost; $destport = $tunnelport; $desturl = $pproxy; print "# tunnelhost = $tunnelhost\n"; print "# tunnelport = $tunnelport\n"; }else{ $destaddr = $pproxyhost; $destport = $pproxyport; $desturl = $pproxyurl; } print "# pproxyhost = $pproxyhost\n"; print "# pproxyport = $pproxyport\n"; print "# pproxyurl = $pproxyurl\n"; print "# bindport = $bindport\n\n"; $|=1; my %children; my $slave; #Прием подключений my $master = HTTP::Daemon->new( LocalPort => $bindport, LocalAddr => 'localhost', Reuse=>1) || die "Can't start server ($@)"; &MainProc($slave) while $slave = $master->accept; sub MainProc { my $conn = shift; #Прием HTTP-запроса my $request = $conn->get_request(); #Ответвление процесса(потока) my $pid = fork(); unless(defined($pid)) { print "# Erorr couldn't fork\n"; close $conn; return; } if($pid) { close $conn; $children{$pid}++; foreach(keys %children ) { my $kid = waitpid($_, &WNOHANG); delete $children{$_} if($kid == -1 || $kid == $_); } return; } #Преобразование HTTP заголовка $request -> remove_header('Proxy-Connection'); $request -> remove_header('Keep-Alive'); $request -> header(Connection=>'close'); my $host = $request -> header('Host'); my $http = $request -> as_string(); my $head_end = index ($http, "\x0A\x0A") + 2; my $head = substr ($http, 0, $head_end); my $post = substr ($http, $head_end); $head =~ s/\x0A/\x0D\x0A/g; $http = $head . $post; $http =~ s/http:\/\/$host//; my $time_start = time; #Отправка запроса прокси и прием результата SendToPProxy( MIME::Base64::encode($host), MIME::Base64::encode($http), $conn ); my $time_end = time; #Лог print &TranslateTimeHour($time_end), " ", $host, " (", &TranslateTime($time_end - $time_start), ")\n"; close $conn; exit; } sub SendToPProxy { my $dest_host = shift; my $query_content = shift; my $clientsock = shift; my $proxysock = IO::Socket::INET->new(Proto=>'tcp',PeerAddr=>$destaddr,PeerPort=>$destport); unless($proxysock) { syswrite($clientsock, $errmsg2, length($errmsg2)); return; } my $post_query; $post_query = 'secret=' . $secret . '&' if defined($secret); $post_query .= 'host=' . $dest_host . '&query=' . $query_content; my $postlen = length($post_query); my $request = "POST $desturl HTTP/1.0\x0D\x0A". "Host: $pproxyhost:$pproxyport\x0D\x0A". "Accept: */*\x0D\x0A". "Content-Type: application/x-www-form-urlencoded\x0D\x0A". "Content-Length: $postlen\x0D\x0A". "User-Agent: $user_agent\x0D\x0A". "Connection: close\x0D\x0A\x0D\x0A" . $post_query; syswrite($proxysock, $request, length($request)); my ($result, $buffer, $response); my $contentstart = -1; my $count = 0; while(1) { $result = sysread($proxysock, $buffer, 1024); last if !defined($result) || !$result; if($contentstart == -1) { $response .= $buffer; last if length($response)>65535; $contentstart = index($response,"\x0D\x0A\x0D\x0A"); next if $contentstart == -1; $buffer = substr($response, $contentstart+4); } $count += length($buffer); syswrite($clientsock, $buffer, length($buffer)); } syswrite($clientsock, $errmsg1, length($errmsg1)) if $count == 0; close $proxysock; } sub TranslateTime { my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(shift); return sprintf "%02u:%02u", $min, $sec; } sub TranslateTimeHour { my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(shift); return sprintf "%02u:%02u:%02u", $hour, $min, $sec; } sub Usage { print "Usage: $0 -px proxy_url [-bp bindport] [-tpx tunnel_proxy] [-pwd secret]\n"; print "Example: $0 -px http://site.com/proxy/proxy.php -bp 8080\n"; print " $0 -px http://site.com/proxy/proxy.php -pwd pproxypass\n"; print " $0 -px http://site.com/proxy/proxy.php -tpx http://localhost:8118\n"; print "\nDefault bind port - 8008\n"; exit; } Использование прокси Допустим адрес pproxy.php будет http://site.com/proxy/pproxy.php Тогда локальный скрипт запускается так: Code: perl plocal.pl -px http://site.com/proxy/pproxy.php По умолчанию открывается порт 8008 Построение цепочки прокси Для примера случай с двумя элементами цепочки. Есть два веб-сервера с двумя скриптами pproxy: http://site1.com/proxy/pproxy.php и http://site2.com/proxy/pproxy.php Локальный скрипт запускается два раза с такими параметрами: Code: perl plocal.pl -px http://site1.com/proxy/pproxy.php perl plocal.pl -px http://site2.com/proxy/pproxy.php -tpx http://localhost:8008 -bp 8009 Браузер следует настроить на 8009 порт и трафик пойдет по такой цепочке: localhost -> site1.com -> site2.com -> target Думаю, несложно будет настроить и на более длинную цепочку. Запуск через Tor Если вы используете Tor вместе с Privoxy (по умолчанию на порту 8118) тогда прокси запускается так: Code: perl plocal.pl -px http://site.com/proxy/pproxy.php -tpx http://localhost:8118 Соответственно http://site.com/proxy/pproxy.php - адрес PHP-прокси К сожалению, через Tor некоторые сайты могут загружаться некорректно, причины пока неизвестны. Установка пароля В pproxy.php раскомментировать строчку, написать там свой пароль PHP: $secret = 'pproxypass'; в параметрах запуска plocal.pl указать его же. Code: perl plocal.pl -px http://site.com/proxy/pproxy.php -pwd pproxypass Примечания 1. Если вы хотите в качестве элемента цепочки узел SOCKS, то Privoxy поможет вам (см пример с Tor) 2. pproxy.php очень компактен и легко может быть внедрен в код сайта, Возможно это немного повысит ваш уровень анонимности. 3. Не стоит забывать что веб-серверы ведут логи и восстановить истинный источник нетрудно даже по цепочке 4. Альтернативный клиент(Delphi/Pascal) с открытым исходным кодом доступен тут http://dump.ru/file/3320224 Спасибо за совет: b3, AkyHa_MaTaTa
нет, HTTPS тут реализовать почти невозможно. HTTPS прокси - он как и сокс основан на постоянном соединении и непрерывном обмене данными. А здесь, как видишь, обмен данными сводится к отсылке HTTP-запроса браузера и получению результата.
А оградить это дело от чужих глаз можно как-то? логин:пасс какой-нибудь. Связка ActivePerl + Opera 9.62 + Proxy, дает в результате тормоза, не в курсе как это можно исправить? хттпс идет как я понял не через проксик, а через впн?
допиши в начало кода РНР, строки: Время выполнения - неограничено. Скрипт не останавливаеться после дисконекта клиента.
Подскажите пожалуйста, почему ActivePerl 5.10 так жестко начинает жрать память через минуту юзания проксика, заодно начинает жрать и браузер (опера и файрфокс, ведут себя одинаково). Как это исправить? п.с. выкиньте с фиксом версию (сет тайм лимит + авторизация)
да, память и процессорное время оно ест немилосердно, это расплата за небольшой размер скрипта. Если слабая машина то могу предложить 1. Снизь приоритет выполнения процесса, это можно сделать в диспетчере задач. (процесс perl.exe) 2. Отключи многопоточность, тогда запросы будут выполняться последовательно и не так загружать процессор. закомментируй эти строчки Code: ... #my $pid = fork(); #unless(defined($pid)) #{ # print "# Erorr couldn't fork\n"; # close $conn; # return; #} #if($pid) #{ # close $conn; # $children{$pid}++; # foreach(keys %children ) # { # my $kid = waitpid($_, &WNOHANG); # delete $children{$_} if($kid == -1 || $kid == $_); # } # return; #} ... и еще команду exit: Code: ... close $conn; #exit; } ...
так, а если залью скрипт в индекс сайта, по идее тяжело будет отпределить кто через них лазел и куда заходил?
в логах апача будет сохранен факт обращения к скрипту с твоего айпи и скорее всего User_Agent перла (User-Agent впрочем можно сменить) То, куда ты обращался через этот скрипт возможно будет сохранено в логах файрволла сервера, это зависит от его настроек.
Нашел себе счастье, спешу поделиться. HTTPTunnel На машине должен быть перл, на серваке можно через перл и обычный пхп. Все очень шустро работает, кучи возможностей: Portmapping, SOCKS4, SOCKS5, веб-админка, авторизация с мускула или ldap. http://sourceforge.net/projects/http-tunnel/ Все шустро работает и носки есть После ппрокси как-будто в летающую тарелку сел... п.с. искал в чем трабл этого скрипта (ппрокси), вроде как в том что use много используется
Только есть маленький не достаток, пока perl не загрузит полность весь фаил он его не выдаст, и из-за этого такие тупки, кто знает к это можно исправить?
DIAgen могу предположить, только, сорри если не прав в php есть команда flush... в перле нет аналогов?
SOCKS здесь вряд ли возможен исходя из самого механизма обращения к прокси. То есть алгоритм такой: 1. perl-скрипт принимает HTTP-запрос и передает POST-параметром к php-скрипту 2. PHP-скрипт принимает POST-параметр и с помощью сокетов посылает целевому серверу, все что вернул ему сервер отправляет назад perl-скрипту для реализации обычного HTTP-запроса этого достаточно. Но SOCKS предусматривает создание постоянного канала, то есть многократный прием и передачу данных. А как передавать данные скрипту второй раз? При таком подходе это невозможно! HTTPS прокси или HTTP CONNECT прокси по сути тот же SOCKS-прокси, так как для подключения к HTTPS нужен сложный протокол - передача сертификатов и т.д. Но если цель не создание полноценного HTTPS-прокси а например просто зайти через прокси на сайт, расположенный на https то все же можно например придумать следующий алгоритм: 1. perl-скрипт принимает запрос как HTTP-прокси (!) и передает его POST-параметром PHP-скрипту 2. PHP-скрипт с помощью curl посылает этот запрос целевому серверу и т.д. в теории это возможно, но при этом возникает некоторые проблемы: - как браузер будет сообщать perl-скрипту что целевой хост именно на https а не на http - на сервере с PHP-прокси будет возникать проблемы c памятью - ведь PHP-скрипту необходмо сначала полностью принять результат и только потом он сможет передать результат и освободить занятую память. (подобно проблемам с памятью в текущей версии perl-скрипта) Плюс еще не на каждом сервере curl доступен P.S. Посмотрел исходник HTTPTunnel. Можно использовать IPC, тогда все проблемы сразу решаются...