Подстановочный IP-запрет с использованием MySQL

Я пытаюсь внедрить систему IP-запрета в свое веб-приложение с использованием MySQL, я знаю, что могу это сделать с использованием .htaccess но это не очень удобно для меня.

В основном моя текущая таблица:

 ip_blacklist(id, ip, date) 

и в php я просматриваю базу данных для IP-клиента, чтобы увидеть, заблокирован ли он или нет:

 $sql = "SELECT ip FROM ip_blacklist WHERE ip = ? LIMIT 1" $query = $this->db->query($sql, array($ip)); if($query->num_rows() > 0){ // Gotcha } 

Теперь .. это работает нормально, но я хочу иметь возможность вводить подстановочные диапазоны IP в базе данных, такие как:

 42.21.58.* 42.21.*.* 53.*.*.* 

Как это сделать?

Заранее спасибо.

Если вы всегда будете проверять один IP-адрес за раз, а запрещенные диапазоны никогда не пересекаются, вы должны сохранить начальный и конечный адреса диапазонов для запрета в числовом формате.

Скажем, вы хотите запретить 192.168.1.0 до 192.168.1.15 что составляет 192.168.1.0/28 .

Вы создаете таблицу следующим образом:

 CREATE TABLE ban (start_ip INT UNSIGNED NOT NULL PRIMARY KEY, end_ip INT UNSIGNED NOT NULL) 

, введите диапазон:

 INSERT INTO ban VALUES (INET_ATON('192.168.1.0'), INET_ATON('192.168.1.0') + POWER(2, 32 - 28) - 1) 

затем проверьте:

 SELECT ( SELECT end_ip FROM ban WHERE start_ip <= INET_ATON('192.168.1.14') ORDER BY start_ip DESC LIMIT 1 ) >= INET_ATON('192.168.1.14') 

Для ORDER BY выполнения запроса требуются части ORDER BY и LIMIT .

Это, как было сказано ранее, предполагает непересекающиеся блоки и один IP за раз.

Если блоки пересекаются (например, вы одновременно запрещаете 192.168.1.0/28 и 192.168.1.0/24 ), запрос может возвращать ложные негативы.

Если вы хотите запросить более одного IP за раз (скажем, обновить таблицу с длинным списком IP-адресов), тогда этот запрос будет неэффективным ( MySQL не оптимизирует range в коррелированных подзапросах)

В обоих случаях вам нужно хранить ваши диапазоны как LineString и использовать пространственные индексы для быстрого поиска:

 CREATE TABLE ban (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, range LINESTRING NOT NULL) ENGINE=MyISAM; CREATE SPATIAL INDEX sx_ban_range ON ban (range); INSERT INTO ban (range) VALUES ( LineString ( Point(INET_ATON('192.168.1.0'), -1), Point(INET_ATON('192.168.1.0') + POWER(2, 32 - 28) - 1), 1) ) ); SELECT * FROM ban WHERE MBRContains(range, Point(INET_ATON('192.168.1.14'), 0)) 

Это сложнее, если вы хотите запретить подсети

Заметки:

  • Картинка «подстановочные знаки» должна использовать 0 (например, 42.21.58.0), которая определяет эту подсеть
  • .0 может не быть подсетью из-за CIDR (может .128, .192 и т. д.),

Так:

  • Хранить IP как 4-байтовый двоичный или unsigned int
  • Храните маску подсети в виде двоичного (или uint) 4 для черного списка подсети
  • Посмотрите на INET_NTOA и INET_ATON для перевода IP-адресов

Тогда предложение WHERE становится

 WHERE ip = @ip --whole IP OR (ip & mask = @ip) --subnet 

Если вы создаете маску 0xffffffff для точных IP-адресов, вы всегда можете сделать ip & mask = @ip , с ip & mask как вычисленный столбец

Кроме того, у вас есть IPv6, чтобы думать слишком

Quick'n'Dirty, но не может использовать правильные индексы:

 SELECT ip FROM ip_blacklist WHERE ? LIKE REPLACE(ip,'*','%') LIMIT 1 

Мое предложение может сделать некоторые складки, но вы, похоже, собираетесь использовать нетрадиционные в этом проекте, поэтому вот оно: Проведите анализ каждого IP-адреса из базы данных в регулярное выражение, которое можно сравнить с IP-адресом пользователя. Пример:

 <?php //Fetch IP's and begin to contruct regex $regex = array(); while($arr = mysql_fetch_array($result)) { $regex[] = '('.$arr['ip'].')'; } $regex = implode('|', $regex); //Regex now becomes (1.1.1.1)|(2.2.2.2)|etc. $regex = str_replace('.', '\.', $regex); //Escape dots for regex $regex = str_replace('*', '((25[0-5])|(2[0-4]\d)|(1\d\d)|(\d\d?))', $regex); //Deal with wildcards $httpVars = array( 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR' ); foreach( $httpVars as $httpVar ) { //No hiding behind proxies if( isset( $IP = $_SERVER[$httpVar] ) ) { break; } } if(preg_match('/^'.$regex.'$/', $IP) != 0) { die(header('HTTP/1.1 403 Forbidden')); //Magical regex says user should be banned } ?> 

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

конвертировать подстановочные знаки с 42.21.*.* до 42.21.0.0 и обратно при записи или чтении записей из базы данных. Для обеспечения эффективности (низкая память и объем диска, производительность) сохраните его как целое число, используйте INET_NTOA и INET_ATON для преобразования.

при поиске IP-адреса abcd:

 SELECT ip FROM ip_blacklist WHERE ip=INET_ATON('abcd') or ip=INET_ATON('abc0') or ip=INET_ATON('ab0.0') or ip=INET_ATON('a.0.0.0') 

Хорошо, последний матч, наверное, глупо .

Не забудьте добавить индексы.