Starlink Mini 2 за CGNAT: как добраться до объекта, который переезжает

Starlink Mini 2 за CGNAT: как добраться до объекта, который переезжает

Starlink Mini 2 за CGNAT: как добраться до объекта, который переезжает

Коротко (проверено 2026-11-11): SpaceX открыла предзаказ Starlink Mini 2 по приглашениям. Это портативная антенна со съёмным аккумулятором. Она подключит к сети больше камер, датчиков, полевых ноутбуков и временных офисов там, где нет оптики. Но стандартный тариф не даст им адреса, к которому можно подключиться из интернета: IPv4 у Starlink по умолчанию работает через операторский NAT (CGNAT), а входящие соединения заблокированы. Мы посчитали IPv4-пространство, которое анонсирует сеть Starlink (990 208 адресов на более чем 12 миллионов клиентов), написали скрипт, который показывает, сидит ли хост за CGNAT, и сравнили четыре способа добраться до объекта, который переезжает.

Источники. Mini 2 по данным Notebookcheck (Daniel Zlatev, 2026-11-10) и DishyTech (Colby Baber, 2026-11-09), обсуждение на Hacker News: 369 баллов и 380 комментариев на 2026-11-11. Политика адресов: статьи Starlink Help Center "What IP address does Starlink provide?" и "IP Address", прочитаны 2026-11-11. Данные маршрутизации: RIPEstat, запрос нашим скриптом 2026-11-11.

Что нового в Mini 2

Параметр Mini 2, по сообщениям от 2026-11-10
Аккумулятор Съёмный, 71 Вт-ч, до 4,5 ч (данные SpaceX), зарядка USB-C 60 Вт
Wi-Fi Wi-Fi 6 (у первой Mini был Wi-Fi 5)
Корпус IP67, 314 x 263 x 23,1 мм, 1,6 кг с аккумулятором и подставкой
Доступность Предзаказ по приглашению для части клиентов в США, другие страны позже
Цена Не определена: комплект $249 и аккумулятор $50 в коде сайта Starlink (DishyTech), в других источниках $999 без разбивки

Данных о скорости не опубликовано. Для этой статьи главное именно аккумулятор: антенну, которая работает четыре часа без электросети, носят с объекта на объект, и это меняет то, как вы добираетесь до всего, что стоит за ней.

Проблема адресов: CGNAT по умолчанию

Справка Starlink описывает две политики IPv4. Стандартная - CGNAT: роутер получает адрес из 100.64.0.0/10, общего диапазона из RFC 6598, много клиентов делят один публичный адрес, а входящий IPv4-трафик запрещён. Публичный IPv4-адрес доступен только как опция на тарифах Priority (две страницы справки называют их по-разному: Local и Global Priority в одной, Priority, Mobile Priority и Maritime в другой). Каждая линия также получает IPv6-префикс /56 через DHCPv6-PD, и Starlink пишет, что нативный IPv6 работает на всех её роутерах, комплектах и тарифах.

Для портативной антенны важны ещё две строки. Собственные Wi-Fi-роутеры Starlink не поддерживают ни проброс портов, ни правила файрвола, ни для IPv4, ни для IPv6. А зарезервированные IPv4-адрес и IPv6-префикс переживают перезагрузку, но могут смениться, когда антенну переносят или обновляется её прошивка. Антенна, которая переезжает, - это адрес, который переезжает.

Почему адресов не хватает на всех: наш подсчёт

Наш скрипт спросил у RIPEstat, что анонсирует AS14593 (SPACEX-STARLINK): 2 274 IPv4-префикса, которые покрывают 990 208 уникальных адресов, в основном /23 и /24. В июне 2026 года Starlink сообщила о более чем 12 миллионах активных клиентов (Mezha). Это примерно один публичный IPv4-адрес на 12 клиентов. По 90-дневной медиане ip4.market для /24, $25,25 за адрес (индекс обновлён 2026-11-02, см. цены на IPv4), один адрес на клиента стоил бы около $303 миллионов; пространство, которое анонсирует AS14593, по этой цене стоит около $25 миллионов. С IPv6 картина обратная: 1 130 префиксов, из них 668 - /40, а один /40 вмещает 65 536 клиентских /56, так что одни эти /40 вмещают около 43,8 миллиона.

Пределы подсчёта: RIPEstat не учитывает маршруты, которые видят меньше 10 пиров RIS, Starlink может пропускать часть трафика через другие AS, а анонсированное пространство - не то же самое, что используемое. Но именно это соотношение объясняет, почему CGNAT стоит по умолчанию.

Скрипт: этот хост за CGNAT?

cgnat_check.js показывает адреса хоста и их тип (публичный, RFC 1918, 100.64.0.0/10), спрашивает у публичной точки, какой адрес она видит, определяет сеть, которая его анонсирует, и говорит, может ли работать входящий IPv4. С номером AS в аргументе он ещё и суммирует пространство, которое анонсирует эта сеть, объединяя перекрывающиеся префиксы.

// cgnat_check.js - am I behind CGNAT, and can anything reach me from outside?
// Usage: bun cgnat_check.js [ASN]    (Bun or Node 18+, no dependencies)
import os from 'node:os'

const v4n = (ip) => ip.split('.').reduce((a, o) => a * 256 + Number(o), 0)
const inV4 = (ip, cidr) => {
  const [net, bits] = cidr.split('/')
  const size = 2 ** (32 - Number(bits))
  return Math.floor(v4n(ip) / size) === Math.floor(v4n(net) / size)
}
const kindV4 = (ip) =>
  inV4(ip, '100.64.0.0/10') ? 'CGNAT (RFC 6598)'
  : ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'].some((c) => inV4(ip, c)) ? 'private (RFC 1918)'
  : inV4(ip, '169.254.0.0/16') ? 'link-local'
  : inV4(ip, '127.0.0.0/8') ? 'loopback' : 'public'
const kindV6 = (ip) => {
  const h = parseInt(ip.split(':')[0] || '0', 16)
  return (h & 0xe000) === 0x2000 ? 'global' : (h & 0xfe00) === 0xfc00 ? 'ULA' : (h & 0xffc0) === 0xfe80 ? 'link-local' : 'other'
}

async function trace(url) {
  try {
    const r = await fetch(url, { signal: AbortSignal.timeout(5000) })
    return Object.fromEntries((await r.text()).trim().split('\n').map((l) => l.split('=')))
  } catch { return null }
}
async function ripestat(call, res) { // RIPEstat sometimes answers an HTML error page: retry
  for (let i = 0; i < 3; i++) {
    try {
      const r = await fetch(`https://stat.ripe.net/data/${call}/data.json?resource=${res}&sourceapp=cgnat-check`, { signal: AbortSignal.timeout(15000) })
      return (await r.json()).data
    } catch { await new Promise((ok) => setTimeout(ok, 2000)) }
  }
  return null
}
async function origin(ip) {
  const d = await ripestat('prefix-overview', ip)
  return d ? { prefix: d.resource, asn: d.asns?.[0]?.asn, holder: d.asns?.[0]?.holder } : {}
}

const local = Object.entries(os.networkInterfaces()).flatMap(([dev, list]) =>
  list.filter((a) => !a.internal).map((a) => ({ dev, ip: a.address, fam: a.family === 'IPv4' || a.family === 4 ? 4 : 6 })))
console.log('Local addresses:')
for (const a of local) console.log(`  ${a.dev.padEnd(8)} ${a.ip.padEnd(40)} ${a.fam === 4 ? kindV4(a.ip) : kindV6(a.ip)}`)

const t4 = await trace('https://1.1.1.1/cdn-cgi/trace')
const t6 = await trace('https://[2606:4700:4700::1111]/cdn-cgi/trace')
const pub4 = t4?.ip, pub6 = t6?.ip
console.log(`\nSeen from outside: IPv4 ${pub4 ?? 'none'}, IPv6 ${pub6 ?? 'none'}`)
if (pub4) {
  const o = await origin(pub4)
  console.log(`  ${pub4} in ${o.prefix ?? '?'} originated by AS${o.asn ?? '?'} ${o.holder ?? ''}`)
  if (o.asn === 14593) console.log('  AS14593 = SpaceX Starlink')
}

const v4local = local.filter((a) => a.fam === 4)
let verdict
if (pub4 && v4local.some((a) => a.ip === pub4)) verdict = 'IPv4: public address on this host, inbound possible (firewall permitting)'
else if (v4local.some((a) => kindV4(a.ip).startsWith('CGNAT'))) verdict = 'IPv4: CGNAT on this host, no inbound IPv4'
else if (pub4) verdict = 'IPv4: behind NAT (home router and maybe CGNAT upstream): inbound only via port forward on every NAT layer'
else verdict = 'IPv4: no IPv4 path out'
console.log(`\n${verdict}`)
if (pub6 && local.some((a) => a.ip === pub6)) console.log('IPv6: global address on this host, inbound depends on the router firewall')
else console.log('IPv6: no global IPv6 path seen')

// Optional: bun cgnat_check.js AS14593 - how much address space does a network announce?
const asArg = process.argv[2]
if (asArg) {
  const ps = (await ripestat('announced-prefixes', asArg)).prefixes.map((p) => p.prefix)
  const v4 = ps.filter((p) => !p.includes(':')).map((p) => { const [n, b] = p.split('/'); return { s: v4n(n), e: v4n(n) + 2 ** (32 - b) } })
    .sort((a, b) => a.s - b.s || b.e - a.e)
  let total = 0, end = -1
  for (const p of v4) { if (p.e > end) { total += p.e - Math.max(p.s, end); end = p.e } }
  const v6 = ps.filter((p) => p.includes(':'))
  const by = (l) => Object.entries(l.reduce((m, p) => ((m[p.split('/')[1]] = (m[p.split('/')[1]] || 0) + 1), m), {})).sort((a, b) => a[0] - b[0]).map(([k, n]) => `/${k} x${n}`).join(', ')
  console.log(`\n${asArg}: ${v4.length} IPv4 prefixes, ${total.toLocaleString('en')} unique addresses; ${v6.length} IPv6 prefixes`)
  console.log(`  IPv4 sizes: ${by(ps.filter((p) => !p.includes(':')))}`)
  console.log(`  IPv6 sizes: ${by(v6)}`)
}
bun cgnat_check.js            # this host only
bun cgnat_check.js AS14593    # plus the space a network announces

Результат, запущено на облачном сервере DCXV с Bun 1.4.4 2026-11-11 (октеты хоста скрыты):

Local addresses:
  eth0     178.23.189.x                             public
  eth0     fe80::x                                  link-local
  int0     10.10.x.x                                private (RFC 1918)
  int0     fe80::x                                  link-local

Seen from outside: IPv4 178.23.189.x, IPv6 none
  178.23.189.x in 178.23.189.0/24 originated by AS204057 DCXV-AS DCXV INTERNATIONAL LTD

IPv4: public address on this host, inbound possible (firewall permitting)
IPv6: no global IPv6 path seen

AS14593: 2274 IPv4 prefixes, 990,208 unique addresses; 1130 IPv6 prefixes
  IPv4 sizes: /20 x2, /21 x28, /22 x58, /23 x1434, /24 x752
  IPv6 sizes: /36 x1, /38 x67, /40 x668, /41 x173, /42 x11, /44 x4, /45 x4, /46 x11, /47 x20, /48 x171

Наш сервер держит публичный адрес на eth0, так что входящие соединения доходят до него, как только их разрешит файрвол; IPv6 на этой машине просто не настроен. Линии Starlink у нас нет, поэтому вывода со Starlink мы не показываем и ничего о нём не утверждаем. Что делает скрипт там: на ноутбуке за роутером Starlink локальный адрес приватный, а публичный лежит в префиксе AS14593, поэтому он сообщает о NAT и печатает строку про Starlink; на WAN-порту стороннего роутера он видит адрес из 100.64.0.0/10 напрямую и сообщает о CGNAT.

Четыре способа добраться до объекта за Starlink

Способ Входящие от любого? Что нужно Что ломает
IPv6 напрямую к объекту Только IPv6-посетители Свой роутер (у Starlink нет правил файрвола) /56 может смениться после переезда; многие сети до сих пор только IPv4
Тариф Priority, публичный IPv4 Да Тариф Priority Адрес может смениться после переезда или обновления прошивки
Mesh VPN (Tailscale, ZeroTier) Только ваши устройства Агент на каждом устройстве Не для публичных посетителей и устройств без агента
Ретранслятор WireGuard на облачном сервере Да, фиксированный IPv4 Небольшой сервер со статическим IPv4 Ещё один хоп и ещё одна машина, которую надо поддерживать

Ретранслятор работает, потому что CGNAT разрешает то, для чего его создали: исходящие соединения. Объект сам подключается к ретранслятору, а PersistentKeepalive = 25 отправляет пакет каждые 25 секунд, чтобы запись NAT оставалась открытой (в quick start WireGuard 25 с названы интервалом, который работает с широким кругом файрволов). Посетители подключаются к адресу ретранслятора, который живёт в дата-центре и не переезжает вместе с антенной.

Ретранслятор: два файла конфигурации

Здесь не запускалось: нужен root на двух машинах и удалённый объект. Это стандартные формы wg-quick; 192.0.2.10 - документационный адрес, подставьте адрес своего ретранслятора.

# /etc/wireguard/wg0.conf on the cloud server (public IPv4 192.0.2.10)
[Interface]
Address = 10.99.0.1/24
ListenPort = 51820
PrivateKey = <relay-private-key>
# forward TCP 443 that arrives on the public address to the site
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 10.99.0.2:443
PostUp = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
PostUp = iptables -A FORWARD -i eth0 -o wg0 -p tcp --dport 443 -j ACCEPT
PostUp = iptables -A FORWARD -i wg0 -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 10.99.0.2:443
PostDown = iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE
PostDown = iptables -D FORWARD -i eth0 -o wg0 -p tcp --dport 443 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

[Peer]
# the site behind Starlink
PublicKey = <site-public-key>
AllowedIPs = 10.99.0.2/32
# /etc/wireguard/wg0.conf at the site (behind CGNAT)
[Interface]
Address = 10.99.0.2/24
PrivateKey = <site-private-key>

[Peer]
PublicKey = <relay-public-key>
Endpoint = 192.0.2.10:51820
AllowedIPs = 10.99.0.1/32
PersistentKeepalive = 25
wg genkey | tee private.key | wg pubkey > public.key
wg-quick up wg0

Три замечания. MASQUERADE делает так, что каждое соединение приходит на объект с 10.99.0.1, и журналы объекта теряют адрес посетителя; если он нужен, уберите это правило и маршрутизируйте ответы объекта обратно через wg0. Каждый пакет теперь проходит и спутниковый канал, и ретранслятор, так что измерьте задержку, прежде чем пускать туда голос или контур управления. И планируйте на случай падения самого канала: глобальный сбой Starlink в июле 2025 года длился больше двух часов.

Размер и цена ретранслятора

Ретранслятор пересылает пакеты и почти ничего больше не делает. Наш облачный тариф Small (2 vCPU, 4 ГБ RAM, 40 ГБ, EUR 15,00 в месяц, один статический IPv4 в цене) в нашем же списке задач рассчитан на WireGuard до 25 туннелей; страница VPN-сервер описывает такую же настройку для команд. Для парка устройств, каждому из которых нужен адрес, см. IoT и дефицит IPv4.

Запустите ретранслятор у нас

Наши дата-центры Tier III в Праге и Ковилье предлагают облачные серверы, каждый со статическим IPv4. Запустите сервер, выполните cgnat_check.js на удалённом объекте и пришлите результат на sales@dcxv.com: мы поможем настроить ретранслятор.

Starlink Mini 2 за CGNAT: как добраться до объекта, который переезжает
networkingcloudinfrastructure

Starlink Mini 2 за CGNAT: как добраться до объекта, который переезжает

Starlink Mini 2 вышла, но стандартные тарифы сидят за CGNAT. Мы насчитали 990 208 IPv4 на 12+ млн клиентов и сравнили четыре способа добраться до объекта.

Трансферы IPv4 в LACNIC 2026: правила, сборы, год удержания
ipv4transfernetworking

Трансферы IPv4 в LACNIC 2026: правила, сборы, год удержания

Трансферы IPv4 в LACNIC в 2026: правила раздела 2.3.2.18, сбор US$ 1,037, NIC.br и NIC México и скрипт по 1,929 трансферам из журнала LACNIC за 2016-2026 годы.

Протесты против дата-центров в Европе: что делать клиентам
clouddedicatedinfrastructure

Протесты против дата-центров в Европе: что делать клиентам

6 ноября сотни людей вошли на стройку дата-центра Microsoft в Амстердаме. Цифры энергосети Нидерландов, измеренная задержка до хабов ЕС и советы клиентам.

Избиратели США отвергли дата-центры 45 к 1: выводы клиентам
clouddedicatedinfrastructure

Избиратели США отвергли дата-центры 45 к 1: выводы клиентам

47 местных голосований о дата-центрах в США, ограничители победили в 45. Мы пересчитали исходные данные и объясняем, что это значит для арендаторов серверов.

AWS BYOIP в 2026: $43.80 за IPv4 или собственный блок /24
ipv4pricingcloud

AWS BYOIP в 2026: $43.80 за IPv4 или собственный блок /24

AWS берет $0.005 в час за публичный IPv4, $43.80 в год. BYOIP без этой платы: когда купленный или арендованный /24 окупается, и скрипт проверки ROA.