Как я определял провайдера по IP

Автор: admin от 15-01-2018, 10:25, посмотрело: 368

Постановка задачи достаточно тривиальна: нужно по IP адресу пользователя определить провайдера. Эти данные далее должны использоваться в своей системе аналитики, а также должна быть возможность сверить их с данными, например Google или Ripe.RIPE. Минусом было то, что вся БД у них хранится в текстовых файлах. Некоторое время «погуглив» я не нашел конвертера для MySQL. Что ж, это немного осложняло задачу. Но не беда, вспоминая достаточно известный ролик и фразу из него: «Ты же специалист», я решил поискать описание файлов БД и написать парсер данных из текстового формата в MySQL.



Но и тут была небольшая засада. Описание полей есть, а вот связи между таблицами мне найти не удалось (может, конечно, плохо искал). А вот это уже стало достаточно ощутимой проблемой.



Далее я начал думать не в правильную сторону и попытался как-то сопоставить поля из файлов «налету». То есть запускал скрипт по парсингу одного файла (очень «костыльно» его парсил, что вспоминаю об этом примерно так: Праздник, ты напился и ничего не помнишь. А на следующий день тебе друзья рассказывают, как ты голый бегал по подъезду и орал матерные частушки и тебе безумно стыдно).



В итоге через несколько часов (да-да, именно через несколько часов) я осознал, что творю ерунду и решили все данные загнать в MySQL. Благо в ходе изучения данных удалось понять какие поля мне нужны для сопоставления.



Импорт данных в нашу БД



Так как поля были известны, я создал следующие таблицы:



CREATE TABLE `test_inetnum` (
  `sip` bigint(20) unsigned NOT NULL,
  `eip` bigint(20) unsigned NOT NULL,
  `org` varchar(255) NOT NULL,
  PRIMARY KEY (`sip`,`eip`),
  KEY `idx_org` (`org`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `test_organization` (
  `organisation` varchar(255) NOT NULL,
  `org-name` varchar(255) NOT NULL,
  PRIMARY KEY (`organisation`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `test_route` (
  `sip` bigint(20) unsigned NOT NULL,
  `eip` bigint(20) unsigned NOT NULL,
  `origin` varchar(255) NOT NULL,
  PRIMARY KEY (`sip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `test_aut_num` (
  `aut-num` varchar(255) NOT NULL,
  `org` varchar(255) NOT NULL,
  PRIMARY KEY (`aut-num`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


Поля в таблицах sip и eip — это декодированные ip2long IP адреса начала диапазона и его конца.



Класс для парсинга данных:



<?php

namespace Ripe;


class Ripe
{
  /**
   * @var string - папка для сохранения файлов от RIPE
   */
  public $folder;

  /**
   * @var int - время, хранения файла
   */
  public $time = 86400;

  /**
   * Ripe constructor.
   *
   * @param string $folder
   */
  function __construct($folder = '')
  {
    if (empty($folder))
    {
      $folder = __DIR__ . '/../../config/ripe';
    }

    if (!is_dir(__DIR__ . '/../../config/ripe'))
    {
      mkdir(__DIR__ . '/../../config/ripe', 0777, true);
    }
    $thisfolder = $folder;
  }

  /**
   * Нужно ли обновлять файл.
   *
   * @param string $file
   *
   * @return bool
   */
  function needUpdate($file = '')
  {
    $current = time();
    if ($current - filectime($thisfolder . '/' . $file) > $thistime)
    {
      return true;
    }
    return false;
  }

  /**
   * Загрузка файла.
   *
   * @param string $url
   */
  function download($url = '')
  {
    if (!empty($url)
        && true === $thisneedUpdate($url)
    )
    {
      system("cd " . realpath($thisfolder) . " && wget ftp://ftp.ripe.net/ripe/dbase/split/" . $url . " && gunzip $url");
    }
  }

  /**
   * Чтение файла по блокам.
   *
   * @param string $file
   * @param        $callback
   */
  function read($file = '', $callback)
  {
    if (is_file($thisfolder . '/' . $file))
    {
      $f = fopen($thisfolder . '/' . $file, 'r');
      if (!empty($f))
      {
        $string = [];
        while (($buffer = fgets($f)) !== false)
        {
          // запоминаем блок
          if ("n" != $buffer)
          {
            $string[] = trim($buffer);
          }
          else
          {
            $blockArray = [];
            // дошли до конца блока
            for ($i = 0; $i < $ic = count($string); $i++)
            {
              if (strpos($string[$i], ': ') === false)
              {
                break;
              }
              else
              {
                $arBlockData = explode(": ", $string[$i]);
                if (!empty($arBlockData))
                {
                  $key = trim($arBlockData[0]);
                  $value = trim($arBlockData[1]);
                  if (!empty($blockArray[$key]))
                  {
                    $blockArray[$key] .= $value . "n";
                  }
                  else
                  {
                    $blockArray[$key] = $value;
                  }
                }
              }
            }
            // callback
            if (!empty($callback)
                && is_callable($callback)
                && !empty($blockArray)
            )
            {
              call_user_func_array($callback, [
                  $blockArray,
                  $file
              ]);
            }
            $string = '';
          }
        }
      }
    }
  }
}


Я пишу данные блоками, то есть блок получили, сразу записали. Для более лучшей производительности конечно же лучше данные вставлять пачками. Код для загрузки и для записи в БД организаций выглядит так:



$ripedownload('ripe.db.organisation.gz');
$riperead('ripe.db.organisation', function ($block, $file) {
  $ripeRoute = new RipeRipeOrganization();
  $ripeRoutesave($block);
});


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



Анализ данных и получение итоговой таблицы.



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



CREATE TABLE `test_ripe` (
  `sip` bigint(20) unsigned NOT NULL,
  `eip` bigint(20) unsigned NOT NULL,
  `org_code` varchar(100) NOT NULL,
  `org_name` varchar(255) NOT NULL,
  PRIMARY KEY (`sip`,`eip`),
  KEY `idx_org_name` (`org_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


Теперь осталось самое простое — это в итоговую таблицу перенести необходимые данные.



Все решается парой запросов:



select * from test_inetnum as t1
inner join test_organization as t2 on t1.org = t2.organisation;

select * from test_route as t1
inner join test_aut_num as t2 on t1.origin = t2.`aut-num`
inner join test_organization as t3 on t2.org = t3.organisation;


Полученные данные загоняем в таблицу test_ripe и получаем счастье.



Результаты



Результаты превзошли мои ожидания. Определение провайдера работает достаточно точно (протестировал на пуле адресов). Также, приятным бонусом оказалось то, что по этой базе определение работает лучше чем у 2ip



Ну и собственно определить провайдера по текущей таблице можно таким запросом:



SELECT * FROM `test_ripe` WHERE `sip` <= '33554435' AND `eip` >= '33554435' ORDER BY `eip` DESC LIMIT 1


Сортировка в запросе обязательно нужна, так как бывает, что один диапазон входит в другой.

Источник: Хабрахабр

Категория: Программирование

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *