Домашний стендовый роутер и тестировщик теплоты
Домашний стендовый роутер и тестировщик теплоты

Тема больших списков IP адресов не перестаёт быть актуальной, не обошла она и меня стороной, вызвав, скорее, академический интерес. Желание покрутить-повертеть базы адресов переросло в проект-хобби выходного дня – одностраничное веб-приложение претендующее на роль помощника системного администратора. Инструментов пока немного, но начало положено, и те, что есть, позволят вдоволь наиграться с любыми базами адресов IPv4 сохранёнными в формате CSV файла.

Страница приложения: http://syo.su

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

Зато нашлось несколько бесплатных IP-Whois API, которые стал коллекционировать в приложении и к которым со временем добавил свой. Было интересно найти производительное решение поиска адреса в базе. На первый взгляд поиск адреса в диапазоне сводится к проверке его целочисленного значения на больше/меньше с целочисленными значениями крайних адресов диапазона: requireAddress BETWEEN networkAddress AND broadcastAddress, либо к выражению: requireAddress & networkBitMask = networkAddress, где к целочисленному значению искомого адреса применяется побитовое И с целочисленным значением битовой маски подсети и сравнивается на равенство со значением первого адреса подсети. Как не строй индексы, всегда получается перебор строк и неудовлетворительное время выполнения. Индексы работают, когда ищешь что-то конкретное, поэтому эффективно искать все возможные значения, например, первого адреса и маски подсети (зависит от того, в чём хранятся подсети в вашей базе) построенных из искомого адреса для всех значений маски в базе:

$ip_address = ip2long("8.8.8.8");
$sql_expressions = array();
for ($i = 3; $i <= 32; $i++)
  $sql_expressions[] = "`networkAddress`=".GetNetworkAddressByMask($ip_address, $i)." AND `networkMask`=".$i;

$sql_query = "SELECT * FROM `table` WHERE ".implode(' OR ', $sql_expressions)." LIMIT 1";

function GetNetworkAddressByMask($ip_address, $ip_mask) {
  return $ip_address & (0xffffffff << (32 - $ip_mask));
}

function GetBroadcastAddressByMask($ip_address, $ip_mask) {
  return $ip_address | (0xffffffff >> $ip_mask);
}

Из полей networkAddress и networkMask в базе строим primary key, если хранящиеся подсети уникальны, либо обычный index, и получаем хорошую скорость поиска адреса в таблице любого размера. Внешний API используется только в этой части приложения, остальные инструменты не требуют Интернета для работы.

Следующий раздел – IP калькулятор. В Интернете много калькуляторов сетей и многими я с удовольствием пользовался, но у всех есть один существенный недостаток – их писали люди, которые ими не пользуются. Их писали программисты, которые вникли в теорию, совершенно верно её реализовали, но они не относятся к тем людям, которые ежедневно считают подсети. В общем, постарался сделать только всё нужное в простом интерфейсе. Кроме преобразования в различные системы записи адресов, так же считаются подсети, входящие в заданную подсеть. По умолчанию отображаются подсети до маски +5 от заданной, этот параметр можно изменить в настройках приложения, вынесен туда, как реже востребованный. Для диапазона адресов, которые не образуют одну подсеть, отображаются подсети между этими адресами – такой, весьма полезной вещи не встречал в других калькуляторах. Выбор маски так же несёт функцию справочника.

Гвоздь программы – инструмент импорта CSV файлов с сетями заданными любой записью, преобразование импортированного во что угодно и экспорт результата во всё мыслимое. Именно этот инструмент является центром приложения на сегодня и именно с ним интересно возиться, когда речь идёт о проекте-хобби. Калькулятор появился как инструмент визуализации парсера при импорте CSV, Whois API как следствие возможностей экспорта. Механизм работы инструмента сводится к следующему: открываем один или несколько файлов CSV содержащих среди столбцов запись сетевого диапазона в том или ином виде (если требуется открыть несколько файлов CSV с сетями разных форматов, с разным набором столбцов, отличающимися записями диапазонов сетей, их следует предварительно привести к одному формату с помощью этого же инструмента), указываем разделитель, наличие строки заголовка, выбора кодировки не стал делать, подразумевается UTF-8. Указываем, как записан диапазон сети в файле, какие столбцы его образуют и в каком формате выражены. Импортируемые файлы можно объединить с другими, одним или несколькими CSV файлами и использовать столбцы обеих полученных таблиц в дальнейших вычислениях. Объединить файлы можно по равенству значений столбцов, например, базы сетей MaxMind имеют в наборе несколько CSV файлов с локализацией, объединяемых с основными файлами адресов по равенству значений столбцов geoname_id, их можно открыть все сразу, как присоединяемые файлы, в этом случае столбцы всех файлов локализаций с одинаковым значением geoname_id будут внесены в один массив в той последовательности, в какой приложение показывает открытые файлы, файлы при открытии сортируются по имени. Проще говоря, происходит LEFT JOIN, при котором все вхождения добавляются справа в одной строке. В случае, как с базами MaxMind, когда число столбцов во всех файлах локализаций одинаковое и гарантировано наличие кодов geoname_id во всех, вполне можно строить выражения включающие столбцы на всех языках. Другой вид объединения файлов – по вхождению импортируемой сети в сеть присоединяемого файла.

Для выполнения условия объединения строк файлов диапазон сети импортируемого файла должен быть равен или меньше диапазона присоединяемого файла. В случае нескольких найденных строк вхождения диапазона, так же столбцы строки следующего найденного диапазона будут добавлены к столбцам предыдущего, и в дальнейших вычислениях можно использовать значения столбцов всех найденных вхождений опираясь на размер массива. Этот вид объединения применим к файлам базы сетей IP2Location, например, можно объединить файл базы DB11 с файлом базы ASN, или ASN с DB1, или разные базы между собой. Более интересный сценарий использования этого объединения – объединить выгрузки своих файрволлов с базами локаций. Можно сравнить файлы на вхождения друг в друга поочередно импортировав и объединив один с другим. Ещё один вид объединения файлов – по истинному значению выражения JavaScript. В выражении можно использовать все столбцы импортируемого и присоединяемого файлов, любое значение выражения, которое будет трактоваться как истина, выполнит объединение строк. Очень медленно работает, используйте только с небольшими данными. Данное объединение происходит только до первого вхождения и следующий поиск начинается с ранее найденной строки, можно ускорить выполнение программы, если предварительно отсортировать объединяемые файлы по столбцам, используемым в выражении объединения.

Далее, переходим к заполнению результатов выполнения импорта. Для каждой импортируемой строки доступны для формирования две переменные – имя листа и комментарий. Количество переменных и их названия остались от ранних версий, когда инструмент писался исключительно для экспорта баз сетей в списки адресов файрволла MikroTik RouterOS, двух вполне хватает для выполнения используемых сценариев экспорта, названия стали привычными и во многих сценариях отражают суть. Имя листа – обязательная к заполнению переменная, строка с пустым значением имени листа не будет загружена. В сценариях, когда нужно сформировать значения более двух переменных для строки, рекомендую все значения помещать в значение переменной комментария, имя листа заполнять значением для сортировки. Значения нескольких переменных можно поместить в переменную комментария как заранее сформированный фрагмент структуры файла экспорта, либо как объявление массива и в дальнейшем обращаться к элементам массива на каждом шаге формирования файла экспорта. Программа предлагает несколько шаблонов выражений JavaScript для формирования переменных, как из одного столбца загружаемых данных, так и сразу из всех, либо только из тех, которые не используются в описании загружаемой сети – именно такие выражения наиболее практичны, когда требуется собрать файл-результат с большим набором полей. Так же, на этом шаге формируется значение переменной строки заголовка. Это не выражение JavaScript, просто строковое значение, которое имеет одно значение для всего цикла экспорта. При наличии заголовков в импортируемых и присоединяемых файлах эту переменную удобно сформировать здесь автоматически и использовать как часть заголовка в файле экспорта.

Все импортируемые файлы рассматриваются как один массив. Число столбцов в файлах может отличаться, главное, чтобы диапазоны сетей были заданы одинаково в одних и тех же столбцах. Строки с пустыми или неверными значениями диапазонов сетей игнорируются. Первым шагом вычисляется объединение файлов, если оно используется. Если объединения не произошло, массив столбцов присоединяемого файла будет иметь размер по количеству столбцов в первом присоединяемом файле и пустую строку в значениях всех элементов. Следующим шагом вычисляется значение имени листа, если результат равен пустой строке, строка импортируемого файла не загружается. Затем вычисляется значение комментария, если задано его выражение. Если загружаемый диапазон не образует одну подсеть, он разбивается на подсети входящие в этот диапазон и загружается отдельными строками с одинаковыми именами листов и значениями комментариев. Пример такого разбиения можно посмотреть в калькуляторе.

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

Есть 4 инструмента, чтобы воспользоваться результатом загруженных данных, вызов любого из них запустит процесс формирования импортируемых данных. Первый – простой поиск одного конкретного адреса в загруженных данных – удобный способ отобразить сформированные данные на примере одной строки. Можно вывести информацию об исходных строках сформировавших подсеть.

Следующий инструмент – построитель файлов экспорта, основное предназначение импорта и обработки данных. Есть несколько блоков выражений, каждый из которых вычисляется и добавляется к строке результирующего файла при наступлении определённых условий. Блок начала файла рассчитывается для каждого результирующего файла первым выражением, в нём используются значения первой строки файла, если использовать имена листов, комментарии и подсети в выражении. Так же, блок конца файла рассчитывается для каждого результирующего файла последним выражением, в нём используются значения последней строки файла. Основной блок рассчитывается и добавляется к результату на каждом шаге для каждой загруженной строки с подсетью. Значение между блоками рассчитывается только, если следующим шагом так же идёт основной блок и не рассчитывается после последнего основного блока файла. При вычислении этого блока используются значения строки идущей перед формируемым разделителем. При группировке по имени листа блок начала группировки рассчитывается сразу после блока начала каждого файла, блок конца после последнего основного блока группы с одинаковыми именами листов, блок между группировками рассчитывается только перед следующим листом и не рассчитывается после последнего блока группировки в файле. Аналогично при группировке по комментариям. При группировке по именам листов и комментариям блоки комментариев идут следом за блоками имён листов в результирующем файле, группируются внутри блоков имён листов. Вот такая незамысловатая схема позволяет получить множество шаблонов реально используемых файлов с данными. Есть много готовых шаблонов, кому-то хватит имеющихся формирований файлов для файрволлов различных систем, кому надо больше, рекомендую ознакомиться с шаблонами JSON, они синтетические, но вполне раскрывают логику построителя. Описание переменных используемых в JavaScript выражениях есть в справочном разделе инструмента импорта за кнопкой "read more…", так же там есть примеры использования.

Третий инструмент – поиск пересекающихся диапазонов в загруженных данных. Хороший инструмент для сравнения различных баз сетей между собой и анализа данных. И четвёртый инструмент – поиск пропущенных адресов в загруженных данных. Поиск можно ограничить, указав подсеть: 0.0.0.0/0, либо диапазон адресов: 0.0.0.0-255.255.255.255, результатом будет файл, содержащий пропущенные подсети. Так же, хороший инструмент анализа баз сетей.

Инструмент импорта поставил несколько интересных вопросов при написании, кажущаяся на первый взгляд простой задача заставила переписывать код неоднократно, каждый раз упрощая структуры в борьбе за память. При работе с базами сетей подразумевается, что вычисляемые значения имён листов и комментариев не уникальны, уникальными являются подсети, значения которых представлены отдельными переменными. При загрузке рассчитанные имена листов и комментарии помещаются в отдельные массивы, массив с загруженной подсетью хранит индексы в этих массивах. Чтобы не хранить одинаковые значения имён листов и комментариев, нужен быстрый поиск уже внесённых в массив значений. Такой поиск обеспечивается именованными массивами, имена которых формируются из 64-битных хэшей построенных из значений строк, а элементами являются индексы в массивах со строками. Здесь использовал функцию построения хэша cyrb53.js, впечатлила своей результативностью, различные эксперименты с построением строковых значений из базы в 8 миллионов строк не приводили к появлению одинакового хэша на разных строках.

Тем не менее, в программе делается проверка для каждого построенного хэша и исходного значения, появление дубликатов исключено, ведётся коррекция одинаковых хэшей, что позволяет так же использовать более простой алгоритм построения 32-битного хэша. Этот же подход используется при объединении файлов по равенству значений столбцов. Так же именованные массивы используются для объединения файлов по вхождению в диапазон сети, строится массив, именами которого являются целочисленные значения первого адреса общей для диапазона подсети. Элементами этого массива являются массивы диапазонов сетей в присоединяемом файле, для которых, соответственно, первый адрес общей подсети равен значению имени в именованном массиве. При поиске вхождения в диапазон перебираются все возможные значения первого адреса подсети для диапазона в импортируемом файле, в случае наличия такого элемента именованного массива ведётся поиск по диапазонам сетей хранящимся в нём. Не SQL, конечно, но в рамках скриптового приложения производительность поиска получается вполне удовлетворительной.

Последний на сегодня инструмент в приложении – разница двух файлов списков адресов файрволла RouterOS. Долгое время от экспериментов со списками адресов на Микротике меня останавливало отсутствие этого инструмента. Общая практика использования баз IP адресов на Микротике сводится к тому, что при каждом обновлении базы адресов предварительно удаляются все ранее внесённые. Это не только не разумно, это убивает флэш при длительном проведении такой процедуры. Инструмент проводит сравнение текущей выгрузки файрволла Микротика с тем, что предполагается загрузить и возвращает файл содержащий команды необходимые для применения изменения списка адресов до новой версии. С этого инструмента началось приложение, в недрах Руборда хранится ранняя версия состоящая только из него. До экспериментов с файрволлом руки пока так и не дошли, загрузил базу по странам на один из своих CHR, убедился, что загрузка работает и увлёкся написанием инструмента импорта CSV, который, в свою очередь, вырос в это приложение.

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

Комментарии (5)


  1. aborouhin
    29.10.2023 12:35

    Инструментов мало не бывает, конечно... но чем вам стандартный ipcalc не угодил? Для его использования, по крайней мере, не надо из консоли в браузер переключаться.

    А для объединения и/или агрегации до заданной маски нескольких очень больших списков адресов/подсетей (вплоть до миллиона строк) я в своё время обнаружил волшебный Perl'овский модуль Net::CIDR::Lite. Я вообще не использую Perl, но для этой задачи из скриптов дёргаю именно его, просто летает по сравнению с другими готовыми решениями. И тут тоже ценность веб-сервиса, IMHO, сомнительна, т.к. такие задачи возникают не разово, а в рамках каких-то автоматически запускаемых задач.


    1. DenSyo Автор
      29.10.2023 12:35

      Ответил ниже, так получилось. Добавлю, что не так давно полюбил веб-разработку и данный проект стал хорошей возможностью пощупать пределы JavaScript при различной организации данных.


  1. DenSyo Автор
    29.10.2023 12:35

    Ставится целью, чтобы приложение было чистым JavaScript - работало как есть в любом браузере в любой среде не требуя наличия Интернета, чтобы любой мог дописать требуемые себе инструменты опираясь на конструкции готовых. Страницу с приложением можно сохранить к себе и пользоваться точно так же, как опубликованной - в этом есть своя прелесть, настоящий кросс-платформ с открытым кодом. Так же, приложение позволяет снизить порог вхождения в тему и пользоваться им людям не владеющим программированием.


  1. vasilijmooduckovic
    29.10.2023 12:35

    диапазоны_IP-адресов.xslx


    1. DenSyo Автор
      29.10.2023 12:35

      Формирование файлов Excel мешает сделать отсутствие zip/unzip в браузерном JS, к сожалению. Есть внешние модули, но тогда приложение перестанет быть самодостаточным для работы.