Android VpnService: bloqueig de lectura TUN enfront d'errors d'escriptura ENOBUFS
Una lectura que no torna mai i una escriptura que falla amb ENOBUFS tenen causes oposades. Mesureu la finestra anunciada i la longitud del paquet per triar la correcció.
Dos símptomes apareixen al mateix bucle de VpnService i s'atribueixen a la mateixa cosa: un lector que no torna mai i una escriptura que falla amb ENOBUFS. No són variacions d'un sol error. Una lectura aturada és un desacord sobre el control de flux amb el peer; una escriptura fallida és un paquet que la interfície no acceptarà. Si els tracteu com un sol problema, passareu un dia ajustant buffers que mai hi van ser. La divisió de sota és com decideixo quina meitat del bucle instrumentar primer.
Un bucle, dos punts de fallada independents
El vostre servei llegeix un paquet IP del descriptor de fitxer TUN, l'inspecciona i escriu una resposta al mateix descriptor. Tot el que es trenca en aquest camí surt en una d'aquestes dues crides, i per això ambdues es reporten com el túnel trencat. Les crides tenen contractes diferents. Una lectura espera que el nucli us lliuri un paquet, i una escriptura lliura al nucli un paquet que té dret a refusar.
Les lectures són la meitat fàcil de raonar. Una lectura que no retorna res o bé espera trànsit que no arriba mai o bé espera perquè el peer ha deixat d'enviar.
Una lectura aturada és un desacord de control de flux
Llegir un dispositiu TUN bloqueja fins que hi ha un paquet disponible. Aquest és el comportament dissenyat, i per això un fil aturat dins de read sembla normal en un enllaç inactiu. Es converteix en un penjament quan la vostra pròpia pila TCP continua acceptant dades de l'aplicació local però no anuncia mai prou finestra perquè el peer remot n'enviï més. El peer omple la finestra que heu anunciat, espera un ACK i una actualització de finestra, i s'atura. Llavors el vostre lector no té res a retornar, i l'aplicació veu una connexió oberta i silenciosa.
Les causes habituals són una pila que escriu al socket sense seguir la finestra anunciada, i una altra que oblida enviar una actualització de finestra després de buidar el seu buffer. Des de fora semblen idèntiques: surten bytes, no torna res, no es produeix cap error. Bloquejar el fil del lector en parseig, resolució DNS o E/S de disc produeix la mateixa forma de símptoma per una raó no relacionada, així que confirmeu que el fil és realment dins de read abans de culpar el control de flux.
Comproveu la cua d'enviament del peer i la finestra que va veure per última vegada. Si el peer reté dades perquè la vostra finestra és zero, la correcció és al vostre camí d'ACK, no a la lectura TUN.
ENOBUFS és un problema de mida, no una manca de buffer
Una escriptura que retorna ENOBUFS en una interfície TUN normalment rebutja la longitud del paquet. La interfície té una MTU, un paquet més gran no es pot encuar, i reintentar el mateix paquet falla igual cada vegada. Aquest determinisme és la pista. Una manca real de buffer s'esvaeix quan baixa la pressió, mentre que un rebuig per mida no té en compte quant espereu.
TCP té un mecanisme exactament per a això, i és el pas que la gent s'estalvia: el clamp MSS. Quan sintetitzeu segments en espai d'usuari trieu la mida del payload, i triar-la de la MTU de la interfície local en lloc de la MTU del túnel emet segments que el túnel no pot portar. Limiteu-vos al més petit dels dos. El clamp només cobreix TCP, així que un datagrama UDP massa gran ha de ser fragmentat o descartat pel vostre codi.
Registreu la longitud de cada paquet que escriviu mentre depureu. La longitud que falla és la resposta, i no es mourà entre reintents.
Tres mesures que separen els casos
No necessiteu un depurador per a cap de les dues fallades. Tres observacions són prou per encaminar-vos a la secció correcta, i triguen menys que una recompilació.
| What you see | Where to look |
|---|---|
| Reader parked, peer send queue empty, last advertised window zero | Window updates in your ACK path |
| Write fails on one length and succeeds on a smaller one | MSS clamp and tunnel MTU |
| Both directions look fine, app still sees no data | Protected socket and routing loop |
Si la tercera fila coincideix, el bucle mateix està bé i el problema és on el túnel no el pot veure: un socket de transport no protegit que porta el vostre propi trànsit VPN de tornada al túnel, cosa que normalment apareix com una connexió que té èxit i després no lliura res.
Repareu la canonada de la qual depenen tots dos camins
Cap símptoma val la pena perseguir-lo fins que la interfície estigui ben construïda. Les crides del builder que importen són l'adreça, la ruta, la MTU i el consentiment, en aquest ordre:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()Poseu la MTU al valor real del túnel en lloc de deixar-la al que feia servir l'exemple. Tot el que escriviu després queda limitat per aquest número, i una discrepància entre la MTU que poseu i l'MSS al qual feu clamp és la manera més habitual de reintroduir la fallada d'escriptura després d'una correcció. Després protegiu el socket que porta el trànsit al mateix servidor VPN. Sense protect, aquests paquets es tornen a encaminar cap al vostre propi túnel i la connexió no s'acaba mai, cosa que sembla una lectura aturada però és un bucle d'encaminament.
El consentiment no es pot saltar. Un servei iniciat sense que prepare retorni null no obté cap interfície, i establish retorna null en lloc de llançar una excepció, així que comproveu el descriptor abans d'iniciar el bucle. La referència de VpnService és el lloc per confirmar el contracte del builder en lloc de copiar un exemple.
Verifiqueu una direcció alhora
Envieu un eco UDP pel túnel i observeu les longituds dels paquets abans de tocar TCP. Després obriu un handshake i confirmeu que els segments es queden per sota de l'MSS que heu calculat. Compteu paquets en lloc de connexions, perquè una aturada aparegui com un buit al comptador en lloc d'un socket penjat. Si les lectures s'aturen després d'una ràfega d'escriptures, la finestra és el sospitós; si les escriptures fallen immediatament, ho és la longitud.
Després reinicieu el dispositiu i repetiu una vegada, perquè un VpnService que només funciona fins al primer canvi de xarxa és un problema d'encaminament, no de control de flux.