Starlink Mini 2 dietro CGNAT: raggiungere un sito che si sposta

Starlink Mini 2 dietro CGNAT: raggiungere un sito che si sposta

Starlink Mini 2 dietro CGNAT: raggiungere un sito che si sposta

In breve (verificato il 2026-11-11): SpaceX ha aperto i preordini su invito della Starlink Mini 2, un'antenna portatile con batteria rimovibile. Porterà in rete più telecamere, sensori, portatili da campo e uffici temporanei dove la fibra non arriva. Quello che un piano standard non darà loro è un indirizzo raggiungibile da internet: l'IPv4 di Starlink passa per impostazione predefinita da un NAT di operatore (CGNAT) e le connessioni in ingresso sono bloccate. Abbiamo contato lo spazio IPv4 annunciato dalla rete Starlink (990.208 indirizzi per oltre 12 milioni di clienti), scritto uno script che dice se un host è dietro CGNAT e confrontato quattro modi per raggiungere un sito che si sposta.

Fonti. La Mini 2 secondo Notebookcheck (Daniel Zlatev, 2026-11-10) e DishyTech (Colby Baber, 2026-11-09), discussa su Hacker News con 369 punti e 380 commenti al 2026-11-11. Politica degli indirizzi: articoli dello Starlink Help Center "What IP address does Starlink provide?" e "IP Address", letti il 2026-11-11. Dati di routing: RIPEstat, interrogato dal nostro script il 2026-11-11.

Cosa c'è di nuovo nella Mini 2

Voce Mini 2, secondo gli articoli del 2026-11-10
Batteria Rimovibile, 71 Wh, fino a 4,5 h (dato SpaceX), ricarica USB-C a 60 W
Wi-Fi Wi-Fi 6 (la prima Mini aveva il Wi-Fi 5)
Struttura IP67, 314 x 263 x 23,1 mm, 1,6 kg con batteria e supporto
Disponibilità Preordine su invito per alcuni clienti negli USA, altri paesi in seguito
Prezzo Non definito: kit a $249 e batteria a $50 nel codice del sito Starlink (DishyTech), altrove $999 senza dettaglio

Non sono stati pubblicati dati di velocità. Per questo articolo conta la batteria: un'antenna che funziona quattro ore senza rete elettrica è un'antenna che si porta da un sito all'altro, e questo cambia il modo di raggiungere ciò che sta dietro.

Il problema degli indirizzi: CGNAT predefinito

Le pagine di aiuto di Starlink descrivono due politiche IPv4. Quella predefinita è il CGNAT: il router riceve un indirizzo da 100.64.0.0/10, l'intervallo condiviso dell'RFC 6598, molti clienti condividono ogni indirizzo pubblico e il traffico IPv4 in ingresso non è consentito. Un indirizzo IPv4 pubblico è un'opzione solo sui piani Priority (le due pagine di aiuto li chiamano in modo diverso: Local e Global Priority in una, Priority, Mobile Priority e Maritime nell'altra). Ogni linea riceve anche un prefisso IPv6 /56 tramite DHCPv6-PD, e Starlink dice che l'IPv6 nativo funziona su tutti i suoi router, kit e piani.

Per un'antenna portatile contano altre due frasi. I router Wi-Fi di Starlink non supportano né il port forwarding né regole di firewall, né in IPv4 né in IPv6. E l'indirizzo IPv4 riservato e il prefisso IPv6 sopravvivono a un riavvio, ma possono cambiare quando l'antenna viene spostata o il suo software si aggiorna. Un'antenna che si sposta è un indirizzo che si sposta.

Perché non c'è un indirizzo per tutti: il nostro conteggio

Il nostro script ha chiesto a RIPEstat cosa annuncia l'AS14593 (SPACEX-STARLINK): 2.274 prefissi IPv4 che coprono 990.208 indirizzi unici, per lo più /23 e /24. A giugno 2026 Starlink ha dichiarato oltre 12 milioni di clienti attivi (Mezha). Fa circa un indirizzo IPv4 pubblico ogni 12 clienti. Alla mediana a 90 giorni di ip4.market per un /24, $25,25 per indirizzo (indice aggiornato il 2026-11-02, vedi prezzi IPv4), un indirizzo per cliente costerebbe circa $303 milioni; lo spazio annunciato dall'AS14593 vale circa $25 milioni a quel prezzo. In IPv6 è il contrario: 1.130 prefissi, 668 dei quali /40, e un /40 contiene 65.536 /56 per clienti, quindi questi soli /40 ne contengono circa 43,8 milioni.

Limiti del conteggio: RIPEstat esclude le rotte viste da meno di 10 peer RIS, Starlink può far passare parte del traffico con altri numeri di AS, e lo spazio annunciato non è quello in uso. Il rapporto spiega comunque perché il CGNAT è l'impostazione predefinita.

Script: questo host è dietro CGNAT?

cgnat_check.js elenca gli indirizzi dell'host e il loro tipo (pubblico, RFC 1918, 100.64.0.0/10), chiede a un punto pubblico quale indirizzo vede, cerca la rete che lo annuncia e dice se l'IPv4 in ingresso può funzionare. Con un numero di AS come argomento somma anche lo spazio annunciato da quella rete, unendo i prefissi sovrapposti.

// 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

Output, eseguito su un server DCXV Cloud con Bun 1.4.4 il 2026-11-11 (ottetti dell'host mascherati):

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

Il nostro server ha l'indirizzo pubblico su eth0, quindi le connessioni in ingresso lo raggiungono appena il firewall le consente; l'IPv6 semplicemente non è configurato su questa macchina. Non abbiamo una linea Starlink, quindi non mostriamo output Starlink e non affermiamo nulla al riguardo. Cosa fa lì lo script: su un portatile dietro il router Starlink l'indirizzo locale è privato e quello pubblico sta in un prefisso dell'AS14593, quindi segnala NAT e stampa la riga Starlink; sulla porta WAN di un router di terze parti vede direttamente l'indirizzo in 100.64.0.0/10 e segnala CGNAT.

Quattro modi per raggiungere un sito dietro Starlink

Modo In ingresso da chiunque? Cosa serve Cosa lo rompe
IPv6 diretto al sito Solo visitatori IPv6 Un router proprio (quello di Starlink non ha regole di firewall) Il /56 può cambiare dopo uno spostamento; molte reti sono ancora solo IPv4
Piano Priority, IPv4 pubblico Sì Un piano Priority L'indirizzo può cambiare dopo uno spostamento o un aggiornamento
VPN mesh (Tailscale, ZeroTier) Solo i tuoi dispositivi Un agente su ogni dispositivo Non adatto a visitatori pubblici o dispositivi senza agente
Relay WireGuard su un server cloud Sì, IPv4 fisso Un piccolo server con IPv4 statico Un hop in più e una macchina in più da gestire

Il relay funziona perché il CGNAT consente ciò per cui è nato: le connessioni in uscita. Il sito chiama il relay, e PersistentKeepalive = 25 invia un pacchetto ogni 25 secondi perché la voce NAT resti aperta (il quick start di WireGuard indica 25 s come intervallo che funziona con un'ampia gamma di firewall). I visitatori si collegano all'indirizzo del relay, che vive in un data center e non si sposta con l'antenna.

Il relay: due file di configurazione

Non eseguito qui: servono root su due macchine e un sito remoto. Sono le forme standard di wg-quick; 192.0.2.10 è un indirizzo di documentazione, mettete al suo posto quello del vostro relay.

# /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

Tre note. MASQUERADE fa arrivare ogni connessione al sito da 10.99.0.1, quindi i suoi log perdono l'indirizzo del visitatore; se serve, togliete quella regola e instradate le risposte del sito indietro via wg0. Ogni pacchetto attraversa ora il collegamento satellitare e il relay, quindi misurate la latenza prima di farci passare voce o un anello di controllo. E prevedete la caduta del collegamento stesso: il guasto globale di Starlink di luglio 2025 è durato più di due ore.

Dimensione e prezzo del relay

Un relay inoltra pacchetti e fa poco altro. Il nostro piano cloud Small (2 vCPU, 4 GB di RAM, 40 GB, EUR 15,00 al mese, un IPv4 statico incluso) è previsto nel nostro elenco di attività per WireGuard fino a 25 tunnel; la pagina server VPN copre la stessa configurazione per i team. Per flotte di dispositivi che hanno bisogno ciascuno di un indirizzo, vedi IoT e la scarsità di IPv4.

Avvia un relay con noi

I nostri data center Tier III a Praga e Covilha ospitano server cloud, ognuno con un IPv4 statico. Avviane uno, esegui cgnat_check.js sul sito remoto e invia l'output a sales@dcxv.com: ti aiutiamo a configurare il relay.

Starlink Mini 2 dietro CGNAT: raggiungere un sito che si sposta
networkingcloudinfrastructure

Starlink Mini 2 dietro CGNAT: raggiungere un sito che si sposta

Arriva la Starlink Mini 2, ma i piani standard stanno dietro CGNAT. Abbiamo contato 990.208 IPv4 per oltre 12 mln di clienti: ecco quattro modalità d'accesso.

Trasferimenti IPv4 LACNIC nel 2026: regole, tariffe, un anno
ipv4transfernetworking

Trasferimenti IPv4 LACNIC nel 2026: regole, tariffe, un anno

Trasferimenti IPv4 in LACNIC nel 2026: regole della sezione 2.3.2.18, tariffa di US$ 1,037, NIC.br e NIC México, e uno script su 1,929 trasferimenti 2016-2026.

Proteste contro i data center in Europa: cosa fare ora
clouddedicatedinfrastructure

Proteste contro i data center in Europa: cosa fare ora

Il 6 novembre centinaia di persone sono entrate nel cantiere Microsoft di Amsterdam. Rete olandese, latenza misurata e cosa fare ora per la continuità.

Voto USA, data center bocciati 45 a 1: lezioni per i clienti
clouddedicatedinfrastructure

Voto USA, data center bocciati 45 a 1: lezioni per i clienti

47 quesiti locali sui data center negli USA, il fronte restrittivo ne ha vinti 45. Ricontiamo i dati grezzi e spieghiamo cosa comporterà per chi affitta server.

AWS BYOIP nel 2026: $43.80 per IPv4 o un tuo blocco /24
ipv4pricingcloud

AWS BYOIP nel 2026: $43.80 per IPv4 o un tuo blocco /24

AWS addebita $0.005 l'ora per IPv4 pubblico, $43.80 all'anno. Il BYOIP è esente: quando un /24 comprato o in affitto si ripaga, con uno script per la ROA.