Android VpnService: TUN окуусунун токтоп калышы ENOBUFS жазуу каталарына каршы
Эч качан кайтпаган окуу жана ENOBUFS менен ийгиликсиз болгон жазуу карама-каршы себептерге ээ. Оңдоону тандоо үчүн жарыяланган терезени жана пакеттин узундугун өлчөңүз.
Бир VpnService циклинде эки симптом пайда болот жана экөөнө тең бирдей нерсе күнөөлөнөт: эч качан кайтпаган окурман жана ENOBUFS менен ийгиликсиз болгон жазуу. Бул бир катанын варианттары эмес. Токтоп калган окуу — теңтушуңуз менен агымды башкаруу боюнча макулдашпастык; ийгиликсиз жазуу — интерфейс кабыл албаган пакет. Аларды бир маселе деп карасак, эч качан катышпаган буферлерди жөнгө салууга бир күн жумшайсыз. Төмөнкү бөлүштүрүү — циклдин кайсы жарымын биринчи өлчөөнү кантип чечкеним.
Бир цикл, эки көз карандысыз бузулуу чекити
Кызматыңыз TUN файл дескрипторунан IP пакетин окуйт, аны текшерет жана ошол эле дескрипторго жооп жазат. Бул жолдо бузулгандын баары ошол эки чакырыктын биринде көрүнөт, ошондуктан экөө тең тоннель бузулду деп билдирилет. Чакырыктардын келишимдери ар башка. Окуу ядро сизге пакет берүүсүн күтөт, ал эми жазуу ядрого четке кагууга укугу бар пакетти берет.
Окуулар — ойлонууга жеңил жарым. Эч нерсе кайтарбаган окуу же эч качан келбеген трафикти күтөт, же теңтуш жөнөтүүнү токтоткондуктан күтөт.
Токтоп калган окуу — агымды башкаруудагы макулдашпастык
TUN түзмөгүн окуу пакет жеткиликтүү болгонго чейин блоктойт. Бул долбоорлонгон жүрүм-турум, ошондуктан read ичинде турган жип бош каналда кадимкидей көрүнөт. Бул илинип калууга айланат, эгер сиздин TCP стегиңиз жергиликтүү колдонмодон дайындарды кабыл алууну улантса, бирок алыскы теңтушкан көбүрөөк жөнөтүү үчүн жетиштүү терезе жарыялабаса. Теңтуш сиз жарыялаган терезени толтурат, ACK жана терезе жаңыртуусун күтөт жана токтойт. Андан кийин окурманыңызда кайтарар эч нерсе жок, ал эми колдонмо ачык жана үнсүз байланышты көрөт.
Кеңири таралган себептер — жарыяланган терезени көзөмөлдөбөй сокетке жазган стек жана буферин бошоткондон кийин терезе жаңыртуусун жөнөтүүнү унуткан стек. Сырттан экөө тең бирдей көрүнөт: байттар чыгат, эч нерсе кайтпайт, ката көтөрүлбөйт. Окуу жибин талдоо, DNS чечүү же диск I/O учурунда блоктоо байланышсыз себептен ушундай эле формадагы симптомду жаратат, ошондуктан агымды башкарууну күнөөлөөдөн мурун жип чын эле read ичинде экенин ырастаңыз.
Теңтушуңуздун жөнөтүү кезегин жана ал акыркы жолу көргөн терезени текшериңиз. Эгер теңтуш сиздин терезеңиз нөл болгондуктан дайындарды кармап турса, оңдоо сиздин ACK жолуңузда, TUN окуусунда эмес.
ENOBUFS — өлчөм маселеси, буфердин жетишсиздиги эмес
TUN интерфейсинде ENOBUFS кайтарган жазуу адатта пакеттин узундугун четке кагат. Интерфейстин MTU бар, андан чоң пакетти кезекке коюу мүмкүн эмес, жана ошол эле пакетти кайра аракет кылуу ар жолу бирдей ийгиликсиз болот. Бул детерминизм — белги. Чыныгы буфер тартыштыгы басым төмөндөгөндө кетет, ал эми өлчөмдөн баш тартуу канча күткөнүңүзгө көңүл бурбайт.
TCP де ушуга так арналган механизм бар, жана ал адамдар өткөрүп жиберген кадам: MSS чектөөсү. Колдонуучу мейкиндигинде сегменттерди синтездегенде пайдалуу жүктүн өлчөмүн өзүңүз тандайсыз, жана аны тоннелдин MTU ордуна жергиликтүү интерфейстин MTU боюнча алуу тоннель ташый албаган сегменттерди чыгарат. Экөөнүн кичинесине чектеңиз. Чектөө тек TCP гана камтыйт, ошондуктан өтө чоң UDP датаграмманы кодуңуз бөлүштүрүүсү же таштоосу керек.
Жөндөө учурунда жазган ар бир пакеттин узундугун журналга жазыңыз. Ийгиликсиз болгон узундук — жооп, жана ал аракеттердин ортосунда өзгөрбөйт.
Учурларды ажыратуучу үч өлчөө
Эки бузулуунун эч бири үчүн дебаггер керек эмес. Үч байкоо өзүңүздү туура бөлүмгө багыттоого жетиштүү, жана алар кайра куруудан аз убакыт алат.
| 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 |
Үчүнчү сап дал келсе, циклдин өзү жакшы жана маселе тоннель көрө албаган жерде: өз VPN трафигиңизди тоннелге кайра ташыган корголбогон транспорттук сокет, бул адатта ийгиликтүү болуп, анан эч нерсе жеткирбеген байланыш катары көрүнөт.
Эки жол тең көз каранды болгон түтүктү оңдоңуз
Интерфейс туура курулмайынча, эч бир симптомдун артынан түшүүнүн мааниси жок. Маанилүү builder чакырыктары — дарек, маршрут, MTU жана макулдук, ушул тартипте:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()MTU ны үлгү колдонгон мааниде калтырбай, тоннелдин чыныгы маанисине коюңуз. Кийин жазган нерсеңиздин баары ошол сан менен чектелген, жана сиз коё турган MTU менен чектеген MSS ортосундагы дал келбестик — оңдоодон кийин жазуу бузулушун кайра киргизүүнүн эң кеңири таралган жолу. Андан кийин VPN серверинин өзүнө трафикти ташыган сокетти коргоңуз. protect жок болсо, бул пакеттер өз тоннелиңизге кайра маршрутталат жана байланыш эч качан аяктабайт, бул токтогон окууга окшош, бирок маршруттоо цикли.
Макулдукту айланып өтүү мүмкүн эмес. prepare null кайтарбай башталган кызмат интерфейс албайт, ал эми establish ыргытуунун ордуна null кайтарат, ошондуктан циклди баштоодон мурун дескрипторду текшериңиз. VpnService маалымдамасы — үлгүнү көчүрүүнүн ордуна builder келишимин ырастоочу жай.
Бир убакта бир багытты текшериңиз
TCP ге тийүүдөн мурун тоннель аркылуу UDP жаңырыгын жөнөтүп, пакеттердин узундуктарын байкаңыз. Андан кийин кол кармашууну ачып, сегменттер сиз эсептеген MSS тен төмөн калаарын ырастаңыз. Байланыштарды эмес, пакеттерди санаңыз, ошондо токтоп калуу илинген сокеттин ордуна эсептегичтеги боштук катары көрүнөт. Эгер жазуулардын жарышынан кийин окуулар токтосо, шектүү — терезе; эгер жазуулар дароо ийгиликсиз болсо, узундук.
Андан кийин түзмөктү кайра жүктөп, бир жолу кайталаңыз, анткени биринчи тармак өзгөрүүсүнө чейин гана иштеген VpnService — агымды башкаруу эмес, маршруттоо маселеси.