fix(admin): считать достижимым только тот адрес Traffic Stats API, который админка действительно опрашивает

Проверка принимала любой ip.IsLoopback(), то есть считала рабочим и 127.0.0.5.
Это неверно: слушатель на конкретном адресе принимает соединения только на него,
а слой proxy обращается строго к http://127.0.0.1:<port>.

  bind 127.0.0.5:38712  ->  dial 127.0.0.1:38712  ->  connection refused
  bind 0.0.0.0:38713    ->  dial 127.0.0.1:38713  ->  connected

Такой адрес выглядел локальным, ломал контур доступа целиком (лимит устройств
fail-closed => не подключается никто) и не вызывал у админки ни одного
возражения. Принимаются ровно 127.0.0.1, 0.0.0.0 и пустой хост.

IPv6-wildcard не принимается сознательно: соединение он принял бы, но HY2XS
объявлен IPv4-only, а зависеть в ответе «достучусь» от net.ipv6.bindv6only
нельзя.

На странице конфигурации мягкое состояние nonCanonicalLoopback убрано: прочий
loopback — это ошибка, а не предупреждение. Осталось три состояния: канон
профиля, wildcard, недостижим.

Свойство закреплено тестом с настоящими сокетами, а гейт приёмки запрещает
возврат IsLoopback() и требует негативного случая 127.0.0.5 в тестах.
This commit is contained in:
2026-09-03 03:53:46 +05:00
parent b315001288
commit b9d3c03f8d
11 changed files with 218 additions and 62 deletions
+16 -6
View File
@@ -347,12 +347,22 @@ Hysteria-интеграции с официальной документацие
странице конфигурации отвечал только на вопрос «достучится ли админка»,
поэтому `0.0.0.0` показывался без замечаний — хотя внутренний Traffic Stats
API при нём опубликован на всех интерфейсах, а оркестратор такой конфигурации
не создаёт. Состояний теперь четыре: канон профиля, wildcard, не-канонический
loopback и недостижимый адрес; backend при этом по-прежнему отвечает только на
вопрос достижимости — превращать лишнюю публикацию в отказ обслуживания
значило бы отключить всех пиров. Заодно исправлено ложное утверждение в
комментарии `parseTrafficStatsPort`: пустой хост `:36712` — это все
интерфейсы, а не loopback.
не создаёт. Теперь показываются три состояния: канон профиля, wildcard
(предупреждение) и недостижимый адрес (ошибка); backend при этом по-прежнему
отвечает только на вопрос достижимости — превращать лишнюю публикацию в отказ
обслуживания значило бы отключить всех пиров. Заодно исправлено ложное
утверждение в комментарии `parseTrafficStatsPort`: пустой хост `:36712` — это
все интерфейсы, а не loopback.
- **«Какой-нибудь loopback» не означает «достижим по 127.0.0.1».** Проверка
принимала любой `ip.IsLoopback()`, то есть считала рабочим и `127.0.0.5`.
Слушатель на конкретном адресе принимает соединения только на него
(`bind 127.0.0.5` + `dial 127.0.0.1` = connection refused, проверено), а слой
proxy обращается строго к `http://127.0.0.1:<port>`: такой адрес выглядел
локальным, ломал контур доступа целиком, и админка об этом молчала.
Принимаются ровно `127.0.0.1`, `0.0.0.0` и пустой хост; свойство закреплено
тестом с настоящими сокетами, а гейт приёмки запрещает возврат `IsLoopback()`
и требует негативного случая в тестах.
### Исправлено — гейты сборки