Bit-flip i store nedlastinger: GRO-feil i gatewayens nettverksdriver

Korrupte store nedlastinger bak en prosumer-gateway skyldtes Generic Receive Offload (GRO) i gatewayens nettverksdriver, og ble fikset med ett ethtool-kall. HTTPS-overføringer døde etter 12 til 15 MB med SSL record-feil. HTTP-overføringer fikk riktig størrelse, men feil innhold, med ulik hash for hvert forsøk. Små filer og strupede overføringer gikk fint, og LAN-trafikk var upåvirket. Alle verter bak gatewayen var rammet, så feilen lå i den felles veien ut mot internett.
Symptomet
HTTPS-testen mot et raskt endepunkt feilet hver gang. curl avsluttet med exit 56 og en OpenSSL-feil:
$ curl -4sL -o /dev/null https://speed.example/100mbMot samme endepunkt, strupet til 1 MB/s, kom 30 av 30 MB fram hver gang. Feilen var altså hastighetsavhengig. Over HTTP ga samme feil riktig antall bytes, men ulik hash:
curl: (56) OpenSSL SSL_read: error:0A000119:SSL routines::decryption failed or bad record mac
$ for i in 1 2; do curl -4s http://mirror/20mb.bin | md5sum; doneDiagnosen
9017c3... -
2bef4a... - # samme fil, samme storrelse, ulik hash
En strupet nedlasting ga en ren referansekopi uten ekstra utstyr. Byte for byte mot de raske kopiene viste den et systematisk mønster:
- Enkeltbyte-feil, 1 til 3 bytes per 20 MB.
- Alltid samme bit som nulles: bit 2, altså 0x04. 0x16 blir 0x12, 0x3d blir 0x39, 0x77 blir 0x73, 0x8e blir 0x8a.
- Tre av fire treff på samme offset innenfor segmentet. Det tyder på en systematisk defekt bit-bane og ikke på sviktende minne.
Den korrupte HTTP-payloaden kom fram til klienten med gyldig TCP-sjekksum. Sjekksummen var altså regnet ut på nytt etter at biten ble endret. Det peker på en komponent som behandler pakkene og regner ut sjekksummer på veien. En feil på linja ville gitt ugyldig sjekksum. TLS-record-MAC-en oppdager endringen, og forbindelsen brytes. HTTP har ingen slik kontroll, og feilen havner i fila. En gyldig TCP-sjekksum utelukker derfor ikke korrupt payload.

Gatewayen eller linja
Omstart av både gateway og fibersentral endret ingenting. En deterministisk feil som overlever omstart, er enten defekt maskinvare eller en programvarefeil. For å teste linja alene ble en liten enkeltkortmaskin koblet direkte til bridge-porten på fibersentralen, utenom gatewayen, med et testskript som kjørte på egen hånd:
- Utenom gatewayen, samme fiber og samme offentlige IP: HTTP 0 av 12 korrupte, HTTPS 2 av 2 store nedlastinger komplette.
- Samme maskin samme kveld, gjennom gatewayen: HTTP korrupt igjen, HTTPS brutt.
Fiber og fibersentral var dermed utelukket, og feilen lå i gatewayen. Test utenom utstyret du mistenker før du kjøper nytt: feilen så ut som defekt maskinvare.
Rotårsaken er GRO
Bit 2-signaturen og hastighetsavhengigheten pekte mot Generic Receive Offload (GRO). GRO slår sammen flere innkommende TCP-segmenter til én stor buffer før de sendes videre opp i stacken. Det sparer CPU ved høy pakkerate. Sammenslåingen og ny utregning av sjekksum skjer i en driverbane som hadde en feil i denne firmwaregrenen. Ved høy pakkerate ble en byte av og til endret under sammenslåingen, og sjekksummen ble regnet ut over den korrupte payloaden. Ved lav pakkerate slås det sammen færre segmenter, og derfor var strupede overføringer alltid rene.
Fiksen er å slå av GRO på WAN-grensesnittet:
ethtool -K eth8 gro offEtterpå var 6 av 6 store HTTPS-nedlastinger komplette og 0 av 30 HTTP-overføringer korrupte. Rett før var tallene 1 av 3 og 2 av 10. Hastigheten var uendret. To firmwareoppgraderinger hadde ikke fjernet feilen.
Gjør fiksen varig
ethtool-innstillingen forsvinner ved omstart. En systemd-unit setter den etter at nettet er oppe:
[Unit]Firmwareoppgraderinger på slike bokser overskriver ofte slike enheter. En cron-jobb på en annen maskin sjekker derfor innstillingen over SSH hvert 30. minutt, setter den på nytt hvis den mangler, og varsler når den har måttet gjøre det:
Description=Disable GRO on WAN (driver bit-flip workaround)
After=network.target
[Service]
Type=oneshot
ExecStart=/sbin/ethtool -K eth8 gro off
[Install]
WantedBy=multi-user.target
*/30 * * * * /opt/udmp_gro_watchdog.sh # sjekk 'ethtool -k eth8 | grep generic-receive-offload'

