Я пытаюсь внедрить систему 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))
Это сложнее, если вы хотите запретить подсети
Заметки:
Так:
Тогда предложение 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')
Хорошо, последний матч, наверное, глупо .
Не забудьте добавить индексы.