Определение прокси на php

Нашел сегодня на форуме вопрос по теме, указанной в заголовке, и решил, что не мешало бы расписать этот механизм в блоге, так как в сети есть достаточно большое количество разнообразных функций, определяющих использование прокси, но большая их часть производит определение не совсем так, как следует.

Итак, прежде всего определим, что определить использование прокси сервера можно далеко не всегда. Имеются прокси-сервера с высоким уровнем анонимности, которые не отдают ни реального адреса пользователя, ни даже информации о самом факте использования прокси. Есть также прокси-сервера среднего уровня анонимности: они передают информацию об использовании прокси-сервера, но не передают реального IP-адреса пользователя. Мы будем работать только с прозрачными прокси-серверами, однако, если Вам нужно и определение самого факта, то можно несколько переделать функцию под свои нужды.

Итак, как же мы, собственно, определим, что пользователь подключен к нашему серверу через прокси? Да очень просто: у нас есть переменная $_SERVER['REMOTE_ADDR'], в которой указан адрес, с которого произошло подключение, а кроме этого есть переменные $_SERVER['HTTP_CLIENT_IP'] и $_SERVER['HTTP_X_FORWARDED_FOR']. В них прокси-сервер передает реальный IP-адрес пользователя. На самом деле таких переменных в сети можно найти достаточно большое количество, но мы будем работать только с этими, так как по моей статистике они используются в 90% случаев, на остальные же приходится по 1% на каждую. Первым этапом сверяем по очереди первую переменную со второй и третьей, однако перед сравнением не лишним было бы проверить содержимое переменных при помощи регулярного выражения, так как передано в них, в принципе, может быть вовсе не то, чего мы ожидаем. Обычной проверки на группы цифр от одной до трех, разделенных точками, здесь будет недостаточно, так как значение октета не может превышать 255.

if(preg_match('/^((\d{1,2}|[01]\d{2}|2([0-4]\d|5[0-5]))\.){3}(\d{1,2}|[01]\d{2}|2([0-4]\d|5[0-5]))$/', $_SERVER['HTTP_CLIENT_IP'])){
   $ip[] = $_SERVER['HTTP_CLIENT_IP'];
}
if(preg_match('/^((\d{1,2}|[01]\d{2}|2([0-4]\d|5[0-5]))\.){3}(\d{1,2}|[01]\d{2}|2([0-4]\d|5[0-5]))$/', $_SERVER['HTTP_X_FORWARDED_FOR'])){
   $ip[] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

Теперь полученные данные нужно сверить с тем IP-адресом, который мы получили в переменной $_SERVER['REMOTE_ADDR'].

$ipcount = count($ip);
if($ipcount == 1){
   if($ip[0] != $_SERVER['REMOTE_ADDR']){
      $prereturn[] = $ip[0];
   }
}else{
   for($ipn=0;$ipn<$ipcont;++$ipn){
      if($ip[$ipn] != $_SERVER['REMOTE_ADDR']){
         if(($ipn>0) and ($ip[$ipn-1] != $ip[$ipn])){
            $prereturn[] = $ip[$ipn];
         }
      }
   }
}
unset($ipcount);
$ipcount = count($prereturn);
if($ipcount == 0){
   $return = $_SERVER['REMOTE_ADDR'];
}elseif($ipcount == 1){
   $return = $prereturn[0];
}else{
   $return = $prereturn[0].','.$prereturn[1];
}

Теперь в переменной $return мы получим либо тот же IP-адрес, что указан в $_SERVER['REMOTE_ADDR'], либо реальный IP-адрес, полученный от прокси-сервера, либо два IP-адреса, указанных через запятую, если в двух заголовках указаны разные IP-адреса. Последнее, как правило, происходит в случаях, если какой-нибудь бот хочет обмануть сервер. Я лично сталкивался только со спам-ботами, у которых были бы разные адреса.

Но это еще не все. Я не стал писать сразу в код, но полученные из заголовков IP-адреса кроме проверки при помощи регулярного выражения нужно еще и проверить на принадлежность к частным сетям, так как некоторые компьютеры (например, в корпоративных сетях) подключаются к сети Интернет через прокси сервер, и в вышеуказанном заголовке будет внутренний адрес компьютера в локальной сети, а он нам не нужен, так как ни для чего не пригодится.

В принципе, эту функцию можно вставить и после проверки регулярным выражением, и после сверки с $_SERVER['REMOTE_ADDR']. Это я оставлю на Ваше усмотрение и напишу функцию полностью отдельно от верхнего кода. Частных сетей у нас на данный момент имеется четыре: 10.0.0.0/8, 172.16.0.0/12 и 192.168.0.0/16 для IPv4 и fc00::/7 для IPv6. С последней мы не будем работать, так как скрипт предназначен только для работы с IPv4 (но если Вам нужно, можно очень легко переделать его и под IPv6, а также сделать комбинированным). Проверять можно через ip2long и как строку. Мне кажется, что проще будет сделать это при помощи строки, но это уже дело вкуса.

function check_local($ip){
   if(substr($ip,0,3) == "10."){
      return false;
   }elseif((substr($ip,0,3) == "172") and ((substr($ip,4,3) >= 16) and (substr($ip,4,3) <= 31)){
      return false;
   }elseif(substr($ip,0,7) == "192.168"){
      return false;
   }else{
      return $ip;
   }
}

Данная функция будет возвращать IP-адрес, если он не имеет отношения к частным сетям, либо false в случае, если переданный IP-адрес таки относится к одной из частных сетей.

Написал, конечно, не очень понятно, но думаю, что разобраться будет не трудно. В WebEngine используется более изощренный механизм проверки, который умеет определять даже Tor Proxy, но части его исходного кода я, к сожалению, не могу использовать в своих проектах, так как функция является интеллектуальной собственностью «Ибице».


10.05.2013, 21:54
  php, proxy, код.
Просмотров: 20923.
6