Vai al contenuto principale

NanoPi R76S: da 600 Mbps a 2 Gbps con OpenWrt

NanoPi R76S: da 600 Mbps a 2 Gbps con OpenWrt

Questa settimana ho sostituito il mio amatissimo EdgeRouter X SFP (un router a mio avviso ottimo per qualità/prezzo ma purtroppo limitato a 1gbps) con un NanoPi R76S, un piccolo router ARMv8 basato su Rockchip RK3576, pubblicizzato con porte 2.5GbE.
Sulla carta, doveva essere perfetto ma nella pratica… non lo era affatto.

Aggiornamento 16 ottobre 2026: se il router si "congela" per 1–2 minuti con i torrent, o la WAN non supera i ~600 Mbps, vai all'aggiornamento in fondo: full-cone NAT e RPS sui device VLAN/PPPoE.

Perché ho scelto il FriendlyELEC NanoPi R76S

Quando ho deciso di sostituire il mio vecchio EdgeRouter X SFP, cercavo qualcosa di compatto, silenzioso e moderno, ma soprattutto abbastanza potente da gestire la mia nuova connessione 2.5gbps FTTH senza sudare troppo. Dopo un po’ di ricerche mi sono imbattuto nel FriendlyELEC NanoPi R76S — un piccolo router con un look quasi da mini NUC, ma costruito attorno a un SoC Rockchip RK3576 con ben otto core ARM (quattro Cortex-A76 e quattro Cortex-A55) e due porte Ethernet 2.5 GbE basate su chip Realtek RTL8125B.

Oltre alla potenza, mi ha convinto anche la forma fisica: il case in alluminio anodizzato nero, le dimensioni davvero ridotte e il fatto che si alimenti tramite USB-C. Nel mio piccolo armadietto server domestico — dove ogni centimetro e ogni alimentatore contano — questo dettaglio ha fatto la differenza. Finalmente niente più brick da 12 V con spinotto, ma un semplice cavo USB-C come quello di uno smartphone.

A bordo c’è FriendlyWrt, la versione personalizzata di OpenWrt mantenuta da FriendlyELEC, che di base è già un ottimo punto di partenza.

La delusione

Dopo l’installazione pulita di FriendlyWrt 24.10 (OpenWrt 24.10), entusiasta di poter finalemnte testare la nuova conenssione multi-gigabit ho eseguito i primi test con la CLI di Ookla (speedtest) pregustando già la soddisfazione di vedere schizzare la banda.

I risultati sono stati deludenti:

TestDownloadUpload
Before tuning606 Mbps771 Mbps

Ho iniziato a fare dei test più approfonditi, htop alla mano, e il collo di bottiglia era evidente: la CPU si saturava su un solo core durante i test, mentre gli altri restavano praticamente inattivi.
Nonostante il flow_offloading fosse attivo nel firewall, il kernel non stava distribuendo correttamente gli interrupt (IRQ) delle interfacce di rete sui vari core.


Diagnosi: IRQ, RPS, XPS e CPU governor

Il SoC RK3576 supporta 8 core, ma senza una corretta gestione delle code e delle interrupt mask, OpenWrt tende ad assegnare tutto a un singolo core (con esattezza lo 0).

Ho quindi individuato tre aree di intervento:

  1. Distribuire gli IRQ delle interfacce di rete (eth0, eth1) su più core CPU
  2. Abilitare e ottimizzare RPS/XPS (Receive/Transmit Packet Steering)
  3. Forzare la CPU in modalità “performance” per evitare scaling aggressivo della frequenza

⚙️ Tuning v1 — RPS/XPS e Sysctl base

La prima iterazione ha incluso:

  • Abilitazione di net.core.rps_sock_flow_entries=65536
  • Configurazione di RPS e XPS su tutti i core (0xff)
  • Applicazione permanente via /etc/hotplug.d/net/99-optimize-network
  • Disattivazione di irqbalance per gestire manualmente le CPU affinity

Risultato

TestDownloadUpload
Tuning v1706 Mbps928 Mbps

Il miglioramento è stato tangibile (almeno lato upload), ma non risolutivo.
La saturazione CPU era distribuita meglio, ma ancora incompleta: i pacchetti non sfruttavano tutti i core disponibili.


🔥 Tuning finale — IRQ affinity + CPU performance mode

Il tuning finale che porta ai ~2 Gbps stabili il NanoPi R76S (RK3576) con FriendlyWrt/OpenWrt 24.10 richiede l'introduzione di diversi parametri finalizzati prevalentemente a distribuire il carico sui (tanti) core disponibili e distribuire i flussi di rete coerentemente.

🧩 1️⃣ Sysctl – Tuning del network stack

📄 /etc/sysctl.d/60-rps.conf

→ imposta le entry per i flussi RPS (Receive Packet Steering)

net.core.rps_sock_flow_entries = 65536

📄 /etc/sysctl.d/99-network-tune.conf

→ parametri generali di rete + TCP tuning (coerenti con BBR)

# Fair Queueing + congestion control
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

# TCP tuning generali
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_tw_reuse = 2
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_fin_timeout = 30

# Gestione traffico burst
net.core.netdev_max_backlog = 250000

⚙️ 2️⃣ Script principale – Applica RPS/XPS manualmente

📄 /usr/local/sbin/apply-rpsxps.sh

→ comando indipendente che puoi eseguire a mano o al boot (applica MASK=ff su eth0 e eth1, e imposta i flussi globali)

#!/bin/sh
MASK_HEX=ff
FLOW_ENTRIES=65536
DEVS="eth0 eth1"
logger -t rpsxps "start apply (devs: $DEVS)"

sysctl -q -w net.core.rps_sock_flow_entries="$FLOW_ENTRIES"

for IF in $DEVS; do
  for RX in /sys/class/net/$IF/queues/rx-*; do
    [ -d "$RX" ] || continue
    echo "$MASK_HEX" > "$RX/rps_cpus"
    echo 32768 > "$RX/rps_flow_cnt" 2>/dev/null
  done
  for TX in /sys/class/net/$IF/queues/tx-*; do
    [ -d "$TX" ] || continue
    echo "$MASK_HEX" > "$TX/xps_cpus"
  done
done
logger -t rpsxps "done apply (mask=$MASK_HEX, flows=$FLOW_ENTRIES)"

📌 Permessi:

chmod +x /usr/local/sbin/apply-rpsxps.sh

🔁 3️⃣ Hook NET – RPS/XPS automatico su ogni interfaccia

📄 /etc/hotplug.d/net/99-optimize-network

Si attiva ogni volta che nasce una nuova interfaccia (eth*, VLAN, PPPoE). Garantisce che anche eth0.835 e pppoe-wan ricevano rps_cpus=ff.

#!/bin/sh
[ "$ACTION" = "add" ] || exit 0
case "$DEVICENAME" in eth*|pppoe-*) : ;; *) exit 0 ;; esac

MASK_HEX=ff
FLOW_ENTRIES=65536
logger -t rpsxps "net hook: $DEVICENAME ACTION=$ACTION (mask=$MASK_HEX flows=$FLOW_ENTRIES)"
sysctl -q -w net.core.rps_sock_flow_entries="$FLOW_ENTRIES"

PARENT="$(basename "$(readlink -f /sys/class/net/$DEVICENAME/lower_* 2>/dev/null || true)")"
[ -n "$PARENT" ] || PARENT="$DEVICENAME"

apply_one() {
  IF="$1"
  for i in 1 2 3 4 5; do
    [ -e "/sys/class/net/$IF/queues/rx-0/rps_cpus" ] && break
    sleep 1
  done
  for RX in /sys/class/net/"$IF"/queues/rx-*; do
    [ -e "$RX/rps_cpus" ] || continue
    echo "$MASK_HEX" > "$RX/rps_cpus"
    echo 32768 > "$RX/rps_flow_cnt" 2>/dev/null
  done
  for TX in /sys/class/net/"$IF"/queues/tx-*; do
    [ -e "$TX/xps_cpus" ] || continue
    echo "$MASK_HEX" > "$TX/xps_cpus"
  done
}

apply_one "$PARENT"
apply_one "$DEVICENAME"

📌 Permessi:

chmod +x /etc/hotplug.d/net/99-optimize-network

🪝 4️⃣ Hook IFACE – Fallback su evento ifup

📄 /etc/hotplug.d/iface/99-rpsxps

Serve come “cintura di sicurezza” in caso di ricreazione PPPoE o WAN da script esterni (es. DDNS)

#!/bin/sh
[ "$ACTION" = "ifup" ] || exit 0

case "$INTERFACE" in
  wan|lan)
    /bin/sh -c "sleep 1; /usr/local/sbin/apply-rpsxps.sh"
    ;;
esac

📌 Permessi:

chmod +x /etc/hotplug.d/iface/99-rpsxps

🚀 5️⃣ Esecuzione al boot (failsafe)

📄 /etc/rc.local

Garantisce che lo script venga lanciato anche all’avvio completo del sistema (dopo init)

/usr/local/sbin/apply-rpsxps.sh || true
exit 0

🧠 6️⃣ (Opzionale) CPU governor

Se vuoi forzare il governor conservative o performance all’avvio:

📄 /etc/rc.local (appendi sotto la riga apply-rpsxps.sh)

for g in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
    echo conservative > "$g"
done

🧾 7️⃣ Verifica post-boot

Comandi di controllo:

logread -e rpsxps | tail -n 20
grep . /sys/class/net/eth{0,1}/queues/{rx,tx}-*/rps_cpus
sysctl net.core.rps_sock_flow_entries

Output atteso:

rpsxps: net hook: eth0.835 ACTION=add ...
rpsxps: net hook: pppoe-wan ACTION=add ...
...
/sys/class/net/eth0.835/queues/rx-0/rps_cpus: ff
/sys/class/net/eth0/queues/tx-0/xps_cpus: ff
/sys/class/net/eth1/queues/tx-0/xps_cpus: ff
net.core.rps_sock_flow_entries = 65536

🚀 Risultati finali

Dopo il reboot e una verifica completa dei parametri, il risultato è stato spettacolare:

TestDownloadUpload
Before606 Mbps771 Mbps
Tuning v1706 Mbps928 Mbps
Tuning finale🟢 2162 Mbps🟢 1038 Mbps

Conclusioni

Il NanoPi R76S è un hardware eccezionale, ma per sfruttarlo al massimo serve una configurazione mirata. Di default, OpenWrt/FriendlyWrt non imposta:

  • IRQ affinity efficiente
  • RPS/XPS su tutti i core

Con pochi accorgimenti, questo piccolo router passa da midrange a full 2.5 Gbps router. In barba a cosa si possa pensare dei soft router.


Aggiornamento 16 ottobre 2026: stabilità, non solo velocità

Un anno dopo, due nuove scoperte. Questa volta riguardano la stabilità, non solo la banda.

TL;DR

  • FriendlyWrt abilita di default il full-cone NAT (nft_fullcone). Costa circa 2 ms di CPU per ogni nuovo flusso UDP in uscita. La DHT di un client torrent genera migliaia di nuovi flussi al secondo → tutti i core saturano → l'intera rete si "congela" per 1–2 minuti. Anche i client cablati. Lo switching puro in LAN continua a funzionare, e per questo sembra un problema di Wi-Fi. Non lo è.
  • La modifica di steering che conta davvero non è sulle NIC fisiche: è sui device WAN virtuali (VLAN + PPPoE), che il packet steering nativo salta del tutto.

1️⃣ Disattiva il full-cone NAT (ovvero: perché il router si blocca a caso)

Verifica:

uci show firewall | grep -i fullcone

Se qualcosa vale 1, impostalo a 0 sulla zona wan (fullcone, fullcone4, fullcone6, di solito @zone[1]: verifica con uci show firewall), poi:

uci commit firewall && service firewall restart

Il masquerade classico va benissimo per un uso domestico tipico. Unico compromesso: con UPnP spento il NAT diventa più restrittivo, e qualche console potrebbe richiedere un forward UDP manuale.

I numeri sul mio router: con il fullcone attivo, un burst DHT di Deluge bloccava il router per ~137 s alla volta. Con il fullcone spento: 65 minuti di carico DHT intenso, zero pacchetti persi, picco del 27% su un singolo core.

2️⃣ Steering lato LAN: un core non basta

Con il packet steering nativo (packet_steering='1'), eth1 riceve una maschera RPS a singolo core (04 nel mio caso). Per il traffico normale va bene, ma un burst di nuovi flussi accoda tutto su quell'unico core. Ho catturato 38.041 pacchetti in backlog sulla CPU2 per ~137 s. Lo stesso burst con RPS ff si è smaltito in circa 1 s:

grep . /sys/class/net/eth1/queues/rx-*/rps_cpus   # 04 = singolo core
for RX in /sys/class/net/eth1/queues/rx-*; do echo ff > "$RX/rps_cpus"; done

RPS distribuisce solo l'elaborazione software in ricezione: non sposta gli IRQ hardware. Puoi osservarlo per singola CPU in /proc/net/softnet_stat (su questo kernel 6.1 il backlog è la colonna 12).

3️⃣ Il punto chiave: lo steering nativo salta i device VLAN/PPPoE

A distanza di un anno, questo si è rivelato il vero fix del "router da 600 Mbps". Il packet steering nativo configura solo le NIC fisiche. I miei eth0.835 e pppoe-wan erano fermi a RPS 00 → CPU0 inchiodata al 100% di softirq durante il download, con gli altri core a riposo → ~667 Mbps. Impostando RPS ff + 32768 flow entry su entrambi i device virtuali: 2309 Mbps sullo stesso server di test, con le stesse maschere sulle interfacce fisiche:

for DEV in eth0.835 pppoe-wan; do
  for RX in /sys/class/net/$DEV/queues/rx-*; do
    echo ff > "$RX/rps_cpus"
    echo 32768 > "$RX/rps_flow_cnt"
  done
done

Questo spiega anche perché lo script "tutto ff" descritto sopra funzionava: il suo hook NET copriva per caso anche i device pppoe-* e VLAN. La modifica minima efficace è l'RPS sulla WAN virtuale: non serve ff sulle NIC fisiche, le maschere native 01/04 + XPS ff vanno bene.

Attenzione: questi device vengono ricreati a ogni reboot o riconnessione PPPoE, quindi vanno riconfigurati su add/ifup (gli hook descritti sopra lo fanno già: tieni il caso pppoe-*). E verifica tutte e quattro le interfacce:

grep . /sys/class/net/eth0/queues/rx-*/rps_cpus \
       /sys/class/net/eth1/queues/rx-*/rps_cpus \
       /sys/class/net/eth0.835/queues/rx-*/rps_cpus \
       /sys/class/net/pppoe-wan/queues/rx-*/rps_cpus

Da allora nessun blocco, e il throughput WAN è tornato sopra i 2 Gbps. Due default che FriendlyWrt dovrebbe davvero ripensare: il fullcone attivo out of the box, e un packet steering che ignora i device WAN virtuali 🙂

Storie di musica, startup, digital, coding e qualsiasi cosa mi venga in mente.

Mi trovi (tra gli altri) su:

Non dimenticare la mia pagina autore su Amazon.