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:
+28
-16
@@ -143,8 +143,7 @@ func GetHysteria2ApiPort() (int64, error) {
|
||||
// Что принимается:
|
||||
//
|
||||
// "127.0.0.1:36712" -> канонический production-профиль;
|
||||
// "127.0.0.5:36712" -> любой loopback достижим по loopback;
|
||||
// "0.0.0.0:36712" -> wildcard включает loopback, обмен состоится;
|
||||
// "0.0.0.0:36712" -> wildcard принимает соединение на 127.0.0.1;
|
||||
// ":36712" -> тот же wildcard: пустой хост в Go означает ВСЕ
|
||||
// интерфейсы, а не loopback.
|
||||
//
|
||||
@@ -152,6 +151,24 @@ func GetHysteria2ApiPort() (int64, error) {
|
||||
// РАБОТАЕТ: отказывать в обслуживании из-за лишней публикации внутреннего API
|
||||
// значило бы наказывать за неё отключением всех пиров. Сама лишняя публикация
|
||||
// при этом не остаётся незамеченной — о ней сообщает панель.
|
||||
//
|
||||
// Прочие адреса loopback НЕ принимаются, и это не педантизм. Слушатель,
|
||||
// привязанный к конкретному адресу, принимает соединения только на него:
|
||||
//
|
||||
// bind 127.0.0.5:36712 -> dial 127.0.0.1:36712 -> connection refused
|
||||
//
|
||||
// (проверено). То есть `ip.IsLoopback()` здесь слишком широк: `127.0.0.5`
|
||||
// выглядит «локальным», но control plane при нём уже не работает, а молчаливое
|
||||
// принятие такого адреса означало бы отказ авторизации всем пирам без единой
|
||||
// подсказки о причине.
|
||||
//
|
||||
// IPv6-wildcard (`[::]:36712`) тоже не принимается, хотя на хосте с
|
||||
// `net.ipv6.bindv6only=0` соединение на 127.0.0.1 он принимает. Причин две:
|
||||
// HY2XS объявлен IPv4-only (`HY2XS_IPV6_ENABLED` обязан быть false), и
|
||||
// достижимость такого слушателя зависит от настройки ядра, которой продукт не
|
||||
// управляет. Отвечать «достучусь» на основании чужого sysctl нельзя, а
|
||||
// указанное в отказе действие — `reconfigure` — для этой конфигурации всё равно
|
||||
// верное.
|
||||
func parseTrafficStatsPort(listen string) (int64, error) {
|
||||
trimmed := strings.TrimSpace(listen)
|
||||
if trimmed == "" {
|
||||
@@ -185,26 +202,21 @@ func parseTrafficStatsPort(listen string) (int64, error) {
|
||||
|
||||
// assertTrafficStatsHostReachable отвечает, сможет ли админка попасть по этому
|
||||
// адресу — а не «нравится ли он профилю».
|
||||
//
|
||||
// Совпадение точное, без `ip.IsLoopback()`: слушатель на конкретном адресе
|
||||
// принимает соединения только на него, поэтому «какой-нибудь loopback» и
|
||||
// «достижим по 127.0.0.1» — разные утверждения. Разбор — в комментарии выше.
|
||||
func assertTrafficStatsHostReachable(host string) error {
|
||||
trimmed := strings.TrimSpace(host)
|
||||
if trimmed == "" {
|
||||
switch strings.TrimSpace(host) {
|
||||
case "", "127.0.0.1", "0.0.0.0":
|
||||
return nil
|
||||
}
|
||||
|
||||
ip := net.ParseIP(trimmed)
|
||||
if ip == nil {
|
||||
return fmt.Errorf(
|
||||
"trafficStats.listen указывает хост %q: HY2XS ожидает IP-адрес loopback (127.0.0.1)",
|
||||
trimmed,
|
||||
)
|
||||
}
|
||||
if ip.IsLoopback() || ip.IsUnspecified() {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"trafficStats.listen слушает %s, а админка обращается к Traffic Stats API только по loopback. "+
|
||||
"trafficStats.listen слушает %s, а админка обращается к Traffic Stats API строго через 127.0.0.1: "+
|
||||
"слушатель на конкретном адресе соединения на 127.0.0.1 не принимает. "+
|
||||
"Пока адреса расходятся, лимит устройств, учёт трафика и принудительное отключение не работают. "+
|
||||
"Верните 127.0.0.1 через `hy2xs-orchestrator reconfigure`",
|
||||
trimmed,
|
||||
strings.TrimSpace(host),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user