Определение IP-адреса пользователя

Bartman

Новичок
Определение IP-адреса пользователя

Приветствую!
Некоторое время назад мне потребовалось написать функцию, определяющую IP-адрес пользователя. Потратив некоторое время на поиски факторов, от которых может зависеть определение IP, я в конце-концов их нашел. Предлагаю всем желающим ознакомится с моими выкладками, оценить их и покритиковать. Буду признателен, если будут внесены поправки и дополния.

Итак, мы разбираем переменные CGI окружения. Тут возможно всего 2 варианта: пользователь работает напрямую (1) и пользователь работает через прокси-сервер (2).


Рассмотрим ПЕРВЫЙ вариант.
Если пользователь работает напрямую, то у нас будет только переменная REMOTE_ADDR, и в ней будет содержаться IP-адрес, с которого пользователь просматривает текущую страницу. То есть, другими словами, IP адрес пользователя, вызвавшего скрипт. Что касается переменной HTTP_X_FORWARDED_FOR, то она будет не определена.


Рассмотрим ВТОРОЙ вариант.
Если пользователь использует proxy сервер, то мы точно будем иметь переменую REMOTE_ADDR - в ней в этом случае хранится адрес прокси-сервера (эта переменная возвращается вместе с заголовком).

В этом варианте возможны несколько вариаций в зависимости от типа прокси-сервера (анализируем HTTP-заголовки).

1. Если используется не анонимный прокси-сервер, то он (прокси-сервер) кладет IP-адрес пользователя в хедер запроса "HTTP_CLIENT_IP" или "HTTP_X_FORWARDED_FOR". Никаких жестких стандартов на это нет и один прокси-сервер может посылать IP пользователя используя переменную "CLIENT_IP", а другой - используя переменную "HTTP_X_FORWARDED_FOR". Между ними нет большой разницы, но они никогда не используются вместе - либо одна, либо другая. Что касается переменной HTTP_X_FORWARDED_FOR - в ней указывается оригинальный адрес посетителя, т.е. с которого он пошел на proxy-server. Но не все прокси его проставляют. Переменная HTTP_X_FORWARDED_FOR может содержать более двух адресов - сколько проксей было пройдено, столько адресов и будет. В случает, если в HTTP_X_FORWARDED_FOR хранится два IP адреса, то первый - это адрес прокси-сервера, а второй адрес клиента.

2. Если proxy-server "полу-анонимный" может быть установлена переменная HTTP_X_FORWARDED - т.е. запрос шел через прокси, но откуда не скажу.

3. Если proxy-server анонимный (или лучше сказать "анонимизирующий" - anonimizer), получателю не будет известно, что используется прокси, и в этом случае HTTP_X_FORWARDED_FOR появляться не будет - IP proxy-сервера будет хранится в REMOTE_ADDR как будто запрос был инициирован оттуда. В поле HTTP_X_FORWARDED_FOR возможно появление значения "unknown" (если в файле конфигурации Squid - squid.conf - установлен параметр "forwarded_for on" - разрешить показывать в посылаемых заголовках адрес клиента сквида. Например, если запрос пришел с машины 192.1.2.3, то в заголовок будет включена строка X-Forwarded-For: 192.1.2.3, если поставить этот параметр в off, то будет включена строка X-Forwarded-For: unknown).


Подытоживая, код функции привожу ниже:

PHP:
function get_ip()
{
   if ($ip = getenv("HTTP_CLIENT_IP")) return $ip;

   if ($ip = getenv("HTTP_X_FORWARDED_FOR")) 
   {
      if ($ip == '' || $ip == "unknown")
      {
          $ip = getenv("REMOTE_ADDR");
      }
      return $ip;
   }

   if ($ip = getenv("REMOTE_ADDR")) return $ip;
}
 

Demiurg

Guest
Если бы я так описывал кадую свою функцию, то наверно уже вышел бы многотомник.
 

Bartman

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

Rin

*
Оцените, плиз.
И вопрос. Какие прокси-сервера создают заголовок HTTP_CLIENT_IP?

PHP:
  //коды возврата функции
  define('HTTP_PROXY_NONE',      1);  //соединение установлено без прокси-сервера
  define('HTTP_PROXY_NORMAL',    2);  //"честный" прокси-сервер
  define('HTTP_PROXY_ANONYMOUS', 3);  //анонимный прокси-сервер

  /**
  * Эта функция возвращает принадлежность http-соединения через прокси-сервер.
  * Анонимные прокси сервера искажают заголовки в возвращаемых сообщениях, 
  * поэтому 100% гарантии нет.
  *
  * @version 1.0.0
  * @return  HTTP_PROXY_*
  * @access  public
  */
  function get_http_proxy() {

    //определяем, что соединение прошло через прокси-сервер по наличию заголовков, 
    //которые создаются только прокси-серверами
    if (!($_SERVER['HTTP_VIA'] ||             //пример: "1.0 cache.tobata.isc.kyutech.ac.jp:8080 (Squid/1.1.18)"
        $_SERVER['HTTP_FROM'] ||              //пример: "xxx.yyy.ru"
        $_SERVER['HTTP_FORWARDED'] ||         //пример: "by [url]http://zip-translator.dna.affrc.go.jp:30001/[/url] (DeleGate/4.4.1) for xxx.yyy.ru"
        $_SERVER['HTTP_PROXY_CONNECTION'] ||  //like HTTP_CONNECTION
        stristr(getenv('HTTP_USER_AGENT'), ' via ') !== false)) {  //пример: "Mozilla/3.04Gold [en] (X11; I; SunOS 5.5.1 sun4u) via NetCache version 3.1.1d-Solaris via NetCache version 3.1.1d-Solaris"
      return HTTP_PROXY_NONE;
    }
    
    //определяем "честного" прокси-сервера
    if ($_SERVER['HTTP_X_FORWARDED_FOR'] && 
        stristr($_SERVER['HTTP_X_FORWARDED_FOR'], 'unknown') === false &&
        $_SERVER['REMOTE_ADDR'] != $_SERVER['HTTP_X_FORWARDED_FOR']) {
      return HTTP_PROXY_NORMAL;
    }
    
    //иначе прокси-сервер анонимный
    return HTTP_PROXY_ANONYMOUS;
  }
 

Rin

*
PHP:
  //коды возврата функции
  define('HTTP_PROXY_NONE',      1);  //соединение установлено без прокси-сервера
  define('HTTP_PROXY_NORMAL',    2);  //"честный" прокси-сервер
  define('HTTP_PROXY_ANONYMOUS', 3);  //анонимный прокси-сервер

  /**
  * Эта функция возвращает принадлежность http-соединения через прокси-сервер.
  * Анонимные прокси сервера искажают заголовки в возвращаемых сообщениях,
  * поэтому 100% гарантии нет.
  *
  * @version 1.0.1
  * @return  HTTP_PROXY_*
  * @access  public
  */
  function get_http_proxy() {

    //определяем, что соединение прошло через прокси-сервер по наличию заголовков,
    //которые создаются только прокси-серверами
    if (!($_SERVER['HTTP_VIA'] ||             //пример: "1.0 cache.tobata.isc.kyutech.ac.jp:8080 (Squid/1.1.18)"
        $_SERVER['HTTP_FROM'] ||              //пример: "xxx.yyy.ru"
        $_SERVER['HTTP_FORWARDED'] ||         //пример: "by http://zip-translator.dna.affrc.go.jp:30001/ (DeleGate/4.4.1) for xxx.yyy.ru"
        $_SERVER['HTTP_PROXY_CONNECTION'] ||  //like HTTP_CONNECTION
        stristr(getenv('HTTP_USER_AGENT'), ' via ') !== false)) {  //пример: "Mozilla/3.04Gold [en] (X11; I; SunOS 5.5.1 sun4u) via NetCache version 3.1.1d-Solaris via NetCache version 3.1.1d-Solaris"
      return HTTP_PROXY_NONE;
    }

    //определяем "честного" прокси-сервера
    if (!$_SERVER['HTTP_X_FORWARDED_FOR'] && $_SERVER['HTTP_CLIENT_IP']) {
      $_SERVER['HTTP_X_FORWARDED_FOR'] = $_SERVER['HTTP_CLIENT_IP'];
    }
    if ($_SERVER['HTTP_X_FORWARDED_FOR'] &&
        preg_match('/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/', $_SERVER['HTTP_X_FORWARDED_FOR']) &&  //IPv4
        $_SERVER['REMOTE_ADDR'] != $_SERVER['HTTP_X_FORWARDED_FOR']) {
      return HTTP_PROXY_NORMAL;
    }

    //иначе прокси-сервер анонимный
    return HTTP_PROXY_ANONYMOUS;

  }
 

[)imon

Guest
Не вчитывался в то, что там написано, но к regexp-у есть замечание:
preg_match('/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/', $_SERVER['HTTP_X_FORWARDED_FOR']) && //IPv4
пропустит такой, например, адрес : 999.999.999.999
 
Сверху