> For the complete documentation index, see [llms.txt](https://docs.ipcheck.ing/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ipcheck.ing/developer/ru/architecture/ip-data-sources.md).

# Источники IP-данных

Два вопроса определяют большую часть MyIP, и на них отвечают разные механизмы:

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

Разделять их намеренно: echo-эндпоинт должен видеть собственное соединение посетителя, поэтому его нельзя проксировать.

```mermaid
flowchart TD
    subgraph Браузер
      G["utils/getips/ — одна функция на источник"]
      T["utils/transform-ip-data.js"]
      C["IP-карточки"]
    end
    subgraph Бэкенд
      H["Геолокационные обработчики в api/<br/>ipinfo-io, ipapi-com, ipapi-is,<br/>ip2location-io, ip-sb, ipcheck-ing, maxmind"]
      M["common/maxmind-service.js — локальная mmdb"]
    end
    P["Сторонние echo-эндпоинты<br/>Cloudflare, IPCheck.ing, IPIP.net, ..."]
    U["Провайдеры геолокации"]

    G -->|"прямой запрос"| P
    G -->|"IP"| H
    H --> U
    H --> M
    H -->|"канонический JSON"| T --> C
```

## Шаг 1 — определение вашего собственного IP

`frontend/utils/getips/` содержит по небольшому модулю на каждый источник. Каждый экспортирует `async` функцию, возвращающую `{ ip, source }`, проверяет результат с помощью `isValidIP()` из `common/valid-ip.js`, а затем проходит через `fetchWithTimeout` (браузерный тайм-аут по умолчанию — 5 секунд).

`frontend/components/IpInfos.vue` рендерит до шести карточек и назначает каждой по одной функции-источнику по индексу:

| Карточка | Основной источник  | Endpoint                               | Резервный источник                                   |
| -------- | ------------------ | -------------------------------------- | ---------------------------------------------------- |
| 0        | IPCheck.ing IPv4   | `4.ipcheck.ing`                        | IPify IPv4 (`api4.ipify.org`)                        |
| 1        | IPCheck.ing IPv6   | `6.ipcheck.ing`                        | IPify IPv6 (`api6.ipify.org`)                        |
| 2        | Cloudflare IPv4    | `1.0.0.1/cdn-cgi/trace`                | MyExternalIP IPv4                                    |
| 3        | Cloudflare IPv6    | `[2606:4700:4700::1111]/cdn-cgi/trace` | MyExternalIP IPv6                                    |
| 4        | IPIP.net           | `myip.ipip.net/json`                   | Upai (`pubstatic.b0.upaiyun.com`)                    |
| 5        | IPCheck.ing IPv6/4 | `64.ipcheck.ing`                       | — (JSON, затем текст того же хоста `/cdn-cgi/trace`) |

{% hint style="info" %}
Три источника IPCheck.ing принимают аргумент `originalSite` . В каноническом развёртывании они читают JSON-эндпоинт; в остальных случаях — Cloudflare-стилизованный `/cdn-cgi/trace` текст того же хоста. Если JSON-вызов не удаётся, они повторяют через trace, прежде чем сдаться.
{% endhint %}

Обработка ошибок организована по уровням:

1. Источник, который выбрасывает исключение или возвращает некорректный IP, пишет `console.warn` и возвращает свой объявленный fallback, либо `{ ip: null, source }`.
2. Каждая карточка запускает собственный конвейер resolve-then-detail, и все шесть работают под `Promise.allSettled`, так что упавший источник не завалит весь пакет. Карточки отрисовываются независимо по мере готовности.
3. Когда вся цепочка карточки ломается, SPA отправляет событие `ip-source:exhausted` в шину приложения. Sentry (если настроен) захватывает его только если другая карточка того же IP-версии всё же отработала — иначе "наша цепочка сломалась" неотличимо от посетителя без IPv6, а это обычный шум.

Отдельные сбои источников `console.warn` предусмотрены по замыслу, поэтому они никогда не доходят до мониторинга ошибок; событие исчерпания по карточке — это сигнал здоровья.

## Шаг 2 — определение геолокации IP

Как только у карточки появляется IP, она вызывает бэкенд. `frontend/data/ip-databases.js` — реестр выбираемых источников:

| id | Название       | Endpoint                                  | Требуется ключ                                   |
| -- | -------------- | ----------------------------------------- | ------------------------------------------------ |
| 0  | IPCheck.ing    | `/api/ipchecking?ip={{ip}}&lang={{lang}}` | `IPCHECKING_API_KEY` (private API)               |
| 1  | IPinfo.io      | `/api/ipinfo?ip={{ip}}`                   | Необязательно (`IPINFO_API_KEY`)                 |
| 2  | IP-API.com     | `/api/ipapicom?ip={{ip}}&lang={{lang}}`   | Нет                                              |
| 3  | IPAPI.is       | `/api/ipapiis?ip={{ip}}`                  | `IPAPIIS_API_KEY`                                |
| 4  | IP2Location.io | `/api/ip2location?ip={{ip}}`              | `IP2LOCATION_API_KEY`                            |
| 5  | IP.sb          | `/api/ipsb?ip={{ip}}`                     | Нет                                              |
| 6  | MaxMind        | `/api/maxmind?ip={{ip}}&lang={{lang}}`    | Локальная база данных, ключ при запросе не нужен |

`buildDbUrl(db, ip, lang)` подставляет `{{ip}}` и `{{lang}}` плейсхолдеры. Флаг `enabled` каждого источника выводится из feature flags на `/api/configs` при их загрузке (`applyConfigAvailability`) — источники, требующие ключ, следуют своему флагу, а источники без ключа всегда доступны — и сбои запросов во время работы на него не влияют. Пользователи переключают источник в Preferences; если сохранённый выбор указывает на источник, который больше не сконфигурирован, он переносится на ближайший доступный (`nearestEnabledId`, проходя вперёд по порядку id) с toast-уведомлением — это единственный случай, когда сохранённая настройка переписывается. Настройка ключей описана в [Optional API Keys](/developer/ru/configuration/optional-api-keys.md).

### Client-side fallback chain

`IpInfos.vue` не просто запрашивает предпочтительный источник и сдаётся. Внутри `fetchIPDetails()`:

* запрошенный источник ищется в **enabled** списке; если его нет (его конфиг-флаг выключен или конфиги ещё не загрузились), обход начинается с первого включённого.
* При ошибке он логирует, переходит к следующему включённому источнику и повторяет попытку — пока не будут испробованы все включённые источники.
* Переход на другой источник, чем был запрошен, показывает одноразовый toast и сдвигает runtime-источник сессии, так что последующие запросы начинают с того, что работает — сохранённая настройка никогда не переписывается.
* Результаты кешируются по IP, а запросы в полёте дедуплицируются по IP, так что шесть карточек с одним и тем же адресом порождают один запрос.

## Каноническая форма ответа

Каждый обработчик геолокации возвращает одинаковый JSON, каким бы ни был upstream. Из `api/ipinfo-io.js`:

```js
{
    ip,
    city,
    region,
    country,        // ISO 3166-1 alpha-2
    country_name,
    country_code,   // тот же код, что и `country`
    latitude,
    longitude,
    asn,            // "AS13335" — с префиксом AS
    org
}
```

| Поле                       | Примечания                                                                                     |
| -------------------------- | ---------------------------------------------------------------------------------------------- |
| `ip`                       | Возвращается upstream'ом обратно                                                               |
| `city` / `region`          | Строки свободной формы; `"N/A"` когда upstream ничего не прислал                               |
| `country` / `country_code` | Оба содержат код ISO alpha-2                                                                   |
| `country_name`             | Собственное название upstream'а — фронтенд обычно заменяет его                                 |
| `latitude` / `longitude`   | Числа                                                                                          |
| `asn`                      | Нормализуется к `AS<number>` форме; источникам, возвращающим просто число, префикс добавляется |
| `org`                      | Название организации или провайдера                                                            |

Два источника его расширяют. `api/ipapi-is.js` добавляет `isHosting` и `isProxy` булевы поля. `api/ipcheck-ing.js` является прокси-прослойкой к приватному API IPCheck.ing и возвращает полезную нагрузку этого API без изменений, включая объект `advancedData` который фронтенд распаковывает в поля proxy, IP-type, native-IP, quality-score, protocol и provider.

<details>

<summary>Что фронтенд делает с полезной нагрузкой</summary>

`frontend/utils/transform-ip-data.js` преобразует канонический ответ в данные карточки:

* `country_name` country **code** локально, чтобы каждый источник показывал одно и то же имя на языке интерфейса; строка upstream используется лишь как запасной вариант.
* `country_code` из `"N/A"` становится пустой строкой.
* `org` становится `isp`, а `asn` начинающийся с `AS` получает `asnlink` на bgp.tools.
* Координаты округляются до одной десятичной для `mapUrl` / `mapUrl_dark`, которые указывают на `/api/map`. На уровне зума карты \~0.176° — это один пиксель, так что 0.1° — субпиксель: маркер выглядит одинаково, а каждый IP в той же ячейке сетки схлопывается в один ключ edge-cache. Координаты полной точности сохраняются для отображения.
* Для источника `0` (IPCheck.ing) он также извлекает `advancedData` поля.

</details>

Добавить источник — значит написать обработчик, который выдаёт такую форму, и добавить строку в `data/ip-databases.js`. Новые обработчики должны использовать фабрику `makeGeoHandler({ name, buildUrl, normalize })` в `common/geo-handler.js`, которая владеет общей оболочкой: читает уже проверенный `?ip`, запрашивает через `fetchUpstream`, выбрасывает исключение при статусе не 2xx (страницы с аварией приходят как HTML и иначе сломали бы `JSON.parse`), нормализует, отвечает и при ошибке логирует и отдаёт 500.

## Локальные наборы данных

Два набора данных лежат на диске внутри `common/` и читаются синхронно во время запроса. У обоих есть автообновитель, который работает в том же процессе.

### MaxMind GeoLite2

`common/maxmind-service.js` открывает `GeoLite2-City.mmdb` и `GeoLite2-ASN.mmdb` из `common/maxmind-db/` и держит оба reader'а в памяти. `lookupMaxMind(ip, lang)` объединяет запись City и запись ASN в каноническую форму, разрешая локализованные названия сначала через английский, а затем через резервный вариант. Если какого-либо reader'а нет, он выбрасывает исключение с `"N/A"` fallback `statusCode` 503, и `/api/maxmind` отвечает 503 — API деградирует, сервер продолжает работать.

Обновление выполняется через `common/maxmind-updater.js`:

| Поведение                       | Значение                                                                                                  |
| ------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Инициализация при запуске       | Скачивает только если файлов нет, с ограничением 5 минут; выполняется независимо от `MAXMIND_AUTO_UPDATE` |
| Первая запланированная проверка | через 60 секунд после запуска                                                                             |
| Интервал повторения             | Каждые 24 часа                                                                                            |
| Условие для планировщика        | `MAXMIND_AUTO_UPDATE=true` плюс `MAXMIND_ACCOUNT_ID` и `MAXMIND_LICENSE_KEY`                              |
| Конкурентность                  | Файл блокировки, считающийся устаревшим через 2 часа                                                      |

Загрузки подготавливаются и публикуются атомарно. Отдельный файловый watcher (`startMaxMindFileWatcher()`) опрашивает оба файла каждые 5 секунд и перезагружает reader'ы, когда другой процесс заменяет их, с debounce в 1 секунду, чтобы City и ASN применялись как одна перезагрузка. Если новый файл некорректен, существующие reader'ы остаются на месте. Инструкции по настройке находятся в [Настройка MaxMind](/developer/ru/getting-started/maxmind-setup.md).

### CAIDA as2org и отношения AS

`common/caida-updater.js` управляет двумя наборами данных с тем же механизмом блокировки / состояния / атомарной публикации / проверки / перезагрузки:

| Набор данных | Файл                               | Источник                                                                                     | Используется в                    |
| ------------ | ---------------------------------- | -------------------------------------------------------------------------------------------- | --------------------------------- |
| `as2org`     | `common/as-org-db/as-org2info.txt` | `publicdata.caida.org/datasets/as-organizations/latest.as-org2info.txt.gz`                   | поиски названия организации по AS |
| `as-rel`     | `common/as-rel-db/as-rel2.txt`     | Самый новый `*.as-rel2.txt.bz2` в `publicdata.caida.org/datasets/as-relationships/serial-2/` | Граф связности AS                 |

`as2org` имеет стабильный `latest` symlink upstream, так что `HEAD` плюс `Last-Modified` достаточно, чтобы обнаружить новый снимок. `as-rel` не имеет такого, поэтому обновитель сканирует список каталога и берёт лексикографически самый новый `YYYYMMDD` файл. Оба варианта распаковываются на лету и проверяются перед публикацией.

Планирование повторяет MaxMind: инициализация при запуске, если файлов нет (лимит 2 минуты), первая проверка через 60 секунд после старта, затем каждые 24 часа; периодический планировщик включается только при `CAIDA_AUTO_UPDATE=true`. Инициализация всегда выполняется, чтобы свежий checkout работал.

`common/as-rel-db.js` разбирает строки, разделённые вертикальной чертой, и оставляет только отношения provider-to-customer (`-1`p2c `), строя индекс customer → providers и счётчик customers на каждый AS. Набор Tier 1 выводится из снимка, а не зашивается в код: AS без провайдеров в топологии p2c, который также предоставляет транзит как минимум 100 другим.` /api/asn-connectivity

`затем выполняет полностью локальный синхронный BFS от исходного AS до Tier 1.` common/as-org-db.js `парсит CAIDA TXT, разделённый вертикальной чертой, (~12 МБ) вместо эквивалентного JSONL (~28 МБ) — содержимое одинаковое, а` split('|') `JSON.parse` работает примерно на 40% быстрее на строку. Оба модуля выбирают самый новый подходящий файл по времени модификации, так что вручную скачанный снимок с другим именем тоже работает.

## Что происходит, когда что-то ломается

| Сбой                                                               | Результат                                                                                                                                       |
| ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Одна конечная точка echo недоступна                                | Его модуль переключается на второй endpoint; если и он не работает, карточка ничего не показывает и выдает `ip-source:exhausted`                |
| Один провайдер геолокации возвращает ошибку                        | Клиент переключается на следующий включённый источник и показывает всплывающее уведомление; последующие запросы начинаются с рабочего источника |
| Все источники геолокации для IP дают сбой                          | Поля деталей карточки остаются пустыми; ошибка записывается на стороне клиента                                                                  |
| Вышестоящий сервис зависает                                        | `fetchUpstream` прерывается через 8 секунд, и обработчик возвращает `500 { error }`                                                             |
| Базы данных MaxMind отсутствуют или недействительны                | `/api/maxmind` возвращает 503; остальная часть API не затрагивается                                                                             |
| Снимки CAIDA отсутствуют                                           | Граф связности возвращается пустым, а поиск по названию организации откатывается к RIPEstat's `as-overview`                                     |
| Вышестоящий сервис возвращает HTML-страницу с сообщением об аварии | `makeGeoHandler` выбрасывает исключение при статусе не 2xx до разбора                                                                           |

## Связанные страницы

* [Бэкенд](/developer/ru/architecture/backend.md) — проверки, уровни кэширования и последовательность запуска
* [Optional API Keys](/developer/ru/configuration/optional-api-keys.md) — какие источники требуют учетные данные
* [Настройка MaxMind](/developer/ru/getting-started/maxmind-setup.md) — получение и обновление GeoLite2
* [Конечные точки API](/developer/ru/reference/api-endpoints.md) — полный контракт запрос/ответ


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ipcheck.ing/developer/ru/architecture/ip-data-sources.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
