PHPFAQ  
Начинающим   Технологии   MySQL   Ошибки   Ссылки   Юмор   О сайте   Форум   PHP Club  

Определение IP адреса

Беллетристика
Теория
Практика.
Примечания.
Комментарии

Беллетристика
Один из самых дремучих вопросов в околопхпешном вебе - это определение IP адреса.
Такого количества неправильного кода не написано, наверное, ни для какой другой операции.

Каждый, кто в один прекрасный день узнаёт о существовании переменной HTTP_X_FORWARDED_FOR, тут же воображает себя мегагуру, и заменяет ей REMOTE_ADDR. Потом приходит знание о других переменных (X_REAL_IP, VIA, и ещё вагон и маленькая тележка), изобретаются многослойные мегаконструкции, изобретатели хвастаются друг перед другом их многоэтажностью и сравнивают свои творения с "кодом из PHPbb!".

При этом спроси любого из них - "какой именно адрес они хотят определить?" - ни один не ответит: понимание основ функционирования сети TCP/IP среди пхп-программистов традиционно слабое.
А вот стремление к нахождению Идеального и Единственно Правильного Решения - традиционно сильное.
В результате вместо IP адреса в логи пишется не пойми что.

К примеру, возьмем, казалось бы, простой вопрос "какой именно IP адрес (из цепочки хостов, через которые идет запрос от компьютера клиента к серверу) мы хотим записать в лог?". После того, как выяснилось, что большинство пхп-программистов затрудняются на него ответить, я и решил написать эту заметку.

А это, между прочим, очень важный вопрос. Не ответив на него, наряду с вопросом "Зачем нам нужен IP адрес?", приступать к самому определению бессмысленно.
При том, что большинству читателей этого текста вопросы покажутся бессмысленными.
Ну что ж, попробуем разобраться.

Теория
Во-первых. Самые азы. Для тех, кто не знает.
Все элементы массива $_SERVER, начинающиеся со слова "HTTP_" - это HTTP-заголовки.
Как уже знают вдумчивые читатели фака на танке, HTTP заголовки присылает клиент. И прислать может любые.
К примеру, заголовок X-All-Your-Base-Belongs-To-Us: Surrender!
Или, как вы уже, наверное, догадались, заголовок X-Forwarded-For: admin durak
Мне кажется, что записывать столь глубокомысленную строку вместо IP адреса - не самая лучшая идея.
Как и вообще доверять любым переменным, начинающимся с HTTP. Это первое правило, которое надо запомнить с молоком матери: Любые элементы массива $_SERVER, начинающиеся с "HTTP_", можно использовать только в справочных целях! К примеру, HTTP_REFERER записываем, чтобы потом посмотреть. Но ни в коем случае не делаем на него Location.

Во-вторых, определимся с тем, ДЛЯ ЧЕГО нам нужен IP адрес. Если мы хотим записать в лог, то пишем однозначно только REMOTE_ADDR. В этой переменной содержится реальный IP адрес реального хоста в интернете, который произвел соединение с нашим сервером. Единственный реальный адрес. Никаких других сервер не знает.
Апач пишет в логи именно REMOTE_ADDR. Не надо считать авторов веб-сервера дурнее себя.

Что значит - реальный IP адрес? А то и значит. Адрес хоста, который произвел соединение с нашим сервером. Этот адрес по определению может быть только один. Один, а не 5 по цепочке. Рассмотрим типичный пример:
Есть пользовательский компьютер, который, который находится в офисной сети. IP компьютера 192.168.0.22
Офисная сеть включена через роутер в сеть здания. IP роутера - 10.10.0.3
Сеть здания, в свою очередь, подключена к интернету, через роутер. IP роутера - 77.88.22.11
Пользователь заходит на сайт, через НТТР прокси. IP прокси - 212.121.0.8
Так вот, сеть TCP/IP так устроена, что каждый следующий узел ничего не знает о предыдущих. Есть только пара хостов, которые соединяются друг с другом. В самих TCP/IP пакетах никакой информации о предыдущих хостах не предусмотрено.
Поэтому, как это ни обидно, но реальным адресом мы можем считать только последний в цепочке - адрес HTTP прокси.

Больше того. Ну допустим, узнали мы адрес компьютера пользователя (чем кичатся многие определители с помощью activeX и ява-апплетов). Этот адрес - 192.168.0.22. Он из приватной сети. Компьютеров с такими адресами в мире - миллионы. Найти компьютер по такому адресу - невозможно. Пользы от него - практически никакой. Практически, но не совсем. Почему? Слушаем дальше:

Поскольку в протоколе HTTP текстовые заголовки, то в них можно добавить свой. Что некоторые хосты и делают. В те самые X-Forwarded-For, Via и прочие.
Можем мы их использовать? Можем. Если правильно понимать - для чего.
Для определения "реального IP адреса", как мы уже убедились - нельзя. А для чего же можно? Например - справки. просто записать, на всякий случай. НО! Только в том случае, если мы откажемся от дурацкой идеи найти один идеальный IP адрес. Если мы не будем писать вместо реального всякую лабуду, а будем записывать все похожее на IP адрес с реальным наряду, то почему нет?
Итак, можно записать всё, похожее на IP адрес. Понадобится выявить злостного вредителя - возможно, какой-то интересный айпишник среди заголовков и проскочит.
Для небольшого повышения надежности сессий - тоже можно. Писать в сессию не только реальный, но и все похожие. И все сверять. Хоть один не совпал - сессию рубим.
И в других подобных случаях.
Не забывая: реальный - отдельно, все похожее на IP адрес - отдельно.
Не забывая: особо полагаться на все эти заголовки не стоит.

Практика.
Итак. Из всего вышеизложенного делается простой вывод.
IP адрес в скрипте может быть только один. Лежит он в переменной REMOTE_ADDR.
Следовательно, вожделенный код получания "идеального IP адреса" выглядит, как
$ip=$_SERVER['REMOTE_ADDR']
Точка.

Далее. Если мы хотим воспользоваться "заголовками, похожими на IP адрес" (лучше всего, во избежание недоразумений, совсем не считать их адресами хостов, а HTTP заголовками особого формата. Тем более, что никакого стандарта на содержимое заголовков X-Forwarded-For, Via и прочих - нет. Там могут оказаться IP адреса чарез запятую, или доменные имена или не через запятую. Не говоря уже о подделках!), то нет смысла судорожно искать все имена заголовков, где может встретиться адрес. Проще искать сами адреса.
Берем, пишем простой код, который в цикле перебирает массив $_SERVER, и регулярным выражением выцепляет все заголовки, в которых встречается подхдящая под шаблон IP адреса строка. Если встретилась, то весь заголовок - с именем и всем содержимым - добавляем в массив или в строку. Которая хранится отдельно от IP адреса, в текстовом виде.
Соответственно, в нужном месте повторяем операцию, и сверяем. IP адрес с IP адресом, строку похожих заголовков - со строкой похожих заголовков.
function get_all_ip() {
  
$ip_pattern="#(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)#";
  
$ret="";
  foreach (
$_SERVER as $k => $v) {
    if (
substr($k,0,5)=="HTTP_" AND preg_match($ip_pattern,$v)) $ret.=$k.": ".$v."\n";
  }
  return 
$ret;
}

Такой вот, несложный код.
Правда, нужда в нем, если задуматься, очень невелика. Разве что, для тех же сессий. А для справки, про запас... не проще ли писать вообще все HTTP заголовки, пришедшие в скрипт? И это поинформативнее будет, чем выцеплять какой-то один адрес из HTTP_X_REAL_IP.
Да и для сессий следует применять с осторожностью - IP адрес может оказаться, к примеру, в реферере...

Примечания.
Недавно я выяснил удивительную вещь. Оказывается, на свете существуют криворукие хостеры, у которых на сервере нет REMOTE_ADDR (а точнее есть, но в нем лежит... адрес самого сервера!). И пихают они адрес удаленного хоста кому куда бог на душу положит. Некоторые - вы будете смеяться - в HTTP_X_FORWARDED_FOR. Говорят, в некоторых больших программных продуктах есть даже специальная настройка для таких случаев - "Получать IP-адреса из заголовка X_FORWARDED_FOR".
Разумеется, этот курьёз не опровергает сказанного выше, и не стоит кидаться писать автоматические определители IP с его учетом. Все подобные случаи должны разбираться только в ручном режиме, самим программистом. Который сначала убедится - где именно в HTTP_X_FORWARDED_FOR лежит нужный адрес - в начале цепочки запятых или в конце, напишет правильный рег, и только потом в настройки сайта добавит код
$_SERVER['REMOTE_ADDR']=get_ip_from_xff();

Примечание для хостеров: mod_realip или mod_rpaf
Примечание для пользователей: разумеется, таких хостеров надо избегать, как калёного железа. Наверняка ведь это не единственная их криворукость?

Другие материалы раздела:
Сессии. Подробное описание работы и объяснение механизма.
Регулярные выражения.
Mail injection. Работа с e-mail средствами PHP.
Безопасность PHP скриптов
Шаблоны


Комментарии

Квинский тип 19.01.12 12:48
Как вывести IP. Просто четко и ясно может кто-нибудь сказать?
Робот 13.01.12 13:54
$_SERVER['REMOTE_ADDR'] ничего не возвращает, в массиве по ключу REMOTE_ADDR => пусто. В чем может быть проблема?
Ответ: неправильная настройка сервера.
El 12.01.12 08:57
Отсутствие REMOTE_ADDR обьясняется не криворукостью хостера, а использованием серверных кластеров.
Ответ: Удивительно, но факт - кластер тоже можно настроить так, чтобы он отдавал скрипту честный IP. Если хостер не криворукий.
Илья 18.12.11 08:40
Ну да немного поразмыслив понял, что то как я сделал, можно использовать лишь при относительной порядочности пользователей, которые пытаются надурить сервер стандартными методами, используя стандартные браузеры и стандартные прокси-сервера, как в большинстве случаев и происходит.
Но если очень умный хакер захочет, сделать аналогичное, то он это сделает, и можно разве что иметь это ввиду и не пытаться делать, что то вроде забора от птиц, а с другой стороны делать так, что да же при такой подмене IP он ни на что существенное (аутидентификация по IP, смена пароля, и прочее) таким образом не повлиял, а лишь мог повторно проголосовать. поставить лишний лайк, просто зайти с забаненного IP создав новый аккаунт и т.д.
Илья 18.12.11 07:52
Конечно то, что на разных хостингах IP может определяться по-разному это полны ппц, но всё же я полагаюсь на стандартное поведение apache-а.
Илья 18.12.11 07:48
Разъясню наверно по понятнее...
При использовании прокси-серверов, в REMORE_ADDR лежит адрес последнего по цепочки прокси, а не реальный адрес пользователя или его роутера, а мне нужно узнать именно его, и записать его в БД, чтобы повторно он проголосовать не смог.
Илья 18.12.11 07:43
А как насчёт прокси-серверов, opera turbo и одновременного использование этого вместе взятого?
При этом IP нужно определить реальный самый близкий к пользователю, для того что бы он не смог проголосовать более одного раза за понравившийся материал, при этом не регистрируясь для голосования?
Я это решил определить так, но судя из этой статьи это вообще не возможно что ли сделать?
$ip = (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) ?$_SERVER['REMOTE_ADDR'] :array_shift(explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']));
OWAP.TJ 06.10.11 19:21
opredelyay host 4erez 1whois.ru
Гугл 02.10.11 23:22
Стало интересно пройти антиспам проверку... сорри
влад 21.09.11 09:39
Здравствуйте, скажите можно ли с вами связаться, для получения личной консультации?

Написать комментарий
Пожалуйста, воздержитесь от посылки спама.
Сообщения, содержащие гиперссылки, проходят премодерацию.
Представьтесь:
Вы робот?
Сообщение:

© phpfaq.ru, 2012
Rambler's Top100 0.022 sec.