Android VpnService-ի մասին չորս ենթադրություն ինձ մի անգամ արժեցան շաբաթվա մեծ մասը, և դրանցից ոչ մեկը ցանցային արձանագրության մասին չէր։ Հավելվածն ասում էր՝ միացված է, բանալու պատկերակը նստած էր կարգավիճակի տողում, և երթևեկությունը դեռ դուրս էր գալիս սովորական երթուղով։ Ես շարունակ ենթադրում էի, որ թունելը իմն է, մինչև չդադարեցնեմ այն, որ իմ ավելացրած երթուղիներն են այն երթուղիները, որ ստանում եմ, և որ stopSelf-ի կանչը ինտերֆեյսը կտանի իր հետ։ Ծառայությունը վարվում էր ճիշտ այնպես, ինչպես փաստաթղթավորված է։ Կոտրված մասը իմ մտավոր մոդելն էր։

Առասպել 1. թունելը մնում է բաց, մինչև դուք կանգնեցնեք այն

Android-ը միաժամանակ պահում է մեկ VPN ինտերֆեյս։ Ստեղծեք երկրորդը, և պլատֆորմը այն հերթ չի դնի, այլ անջատելու է ձերը և ձեր ծառայությունում կկանչի onRevoke()։ Եթե ձեր կոդը քանդում է ամեն ինչ միայն կոճակի սեղմումով, ապա զավթումը հետևում թողնում է դեսկրիպտոր և ընթերցող թել, և հաջորդ մեկնարկը ժառանգում է երկուսին էլ։

VpnService-ի տեղեկանքը հստակ ասում է, որ prepare()-ը վերադարձնում է null, երբ հավելվածն արդեն համաձայնություն ունի, և Intent, երբ օգտատերը դեռ պետք է հաստատի։ Այդ համաձայնությունը հասնում է միայն այն դեպքում, երբ մտադրությունը հանձնեք startActivityForResult()-ի միջոցով։ Բաց թողեք հանձնումը և establish()-ը կվերադարձնի null, ինչը կարդացվում է որպես լուռ ձախողում, այլ ոչ որպես թույլտվության խնդիր։

Always-on VPN-ը ավելացնում է տեր, որին դուք չեք վերահսկում։ Համակարգը կարող է թունելը հետ բերել վերագործարկումից կամ վթարից հետո, և այն առաջ է անցնում օգտատիրոջ կողմից ձեռքով մեկնարկած VPN հավելվածից։ Կառուցեք onRevoke()-ը այնպես, որ այն փակի նույն ռեսուրսները, ինչ ձեր սովորական անջատումը, և վերաբերվեք դրան որպես հիմնական կանգնեցման ուղի, այլ ոչ որպես եզրային դեպք։ Օգտատերերը փոխում են VPN հավելվածները, ձեր կոդը պետք է դիմանա դրան։

Առասպել 2. addRoute-ը որսում է հենց այն, ինչ թվարկել եք

Երթուղիներն են որոշում, թե որ փաթեթներն են հասնում ձեր դեսկրիպտորին, և շրջանակի սխալը ստեղծում է այն կատաղեցնող դեպքը, երբ երթևեկության մի մասը գնում է թունելով, իսկ մի մասը՝ ոչ։ addRoute("0.0.0.0", 0)-ը վերցնում է ամբողջ IPv4-ը, ինչը նշանակում է, որ յուրաքանչյուր փաթեթ այժմ հասնում է ձեզ և պետք է փոխանցվի կամ դեն նետվի։ Մեկ հոսթի համար /32-ը գրեթե ոչինչ չի վերցնում, և մնացած ամեն ինչ վարվում է այնպես, կարծես ձեր VPN-ը երբեք չի տեղադրվել։

Ընտանիքները հաշվվում են առանձին։ Միայն IPv4-ի երթուղին IPv6-ը թողնում է հիմնական ցանցում, այնպես որ երկակի կույտով հոսթը դեռ կարող է լուծել և միանալ թունելից դուրս՝ միաժամանակ ներսում ամբողջովին միացված երևալով։

DNS-ը հետևում է նույն տրամաբանությանը։ addDnsServer()-ով ավելացված սերվերները գնում են այն հավելվածներին, որոնք օգտագործում են թունելի լուծիչը, մինչդեռ սեփական գաղտնագրված DNS վերջնակետով կամ հոսթի անվան վրա դրված Private DNS-ով հավելվածը լրիվ անտեսում է նրանց։ Երբ անունները ձախողվում են մի հավելվածի համար և աշխատում են մյուսի համար, ստուգեք լուծիչի ուղին նախքան թեկուզ մեկ երթուղու դիպչելը։

Առասպել 3. հավելվածային կանոնները անջատիչ են, որը կարելի է թեքել ցանկացած ուղղությամբ

addDisallowedApplication()-ը և addAllowedApplication()-ը երկու ռեժիմ են, ոչ թե երկու զտիչ։ Կառուցիչն ընդունում է մեկ մոդել թունելի համար, ուստի այն հավելվածը, որն ուզում է ամեն ինչ, բացի երեք փաթեթից, օգտագործում է արգելված ցանկը և թողնում է թույլատրված ցանկը դատարկ։ Երկուսը միմյանց բացառում են, և մարդիկ դա իմանում են գործարկման ժամանակ՝ այն բանից հետո, երբ կարգավորումների էկրանն արդեն առաջարկել է երկու անջատիչները։

Փաթեթների տեսանելիությունը նույնպես կծում է։ Android 11-ից սկսած կարող եք անվանել միայն այն փաթեթները, որոնք տեսնում եք, ուստի բացառումների ցանկը, որը կառուցվել է հարցումից, որը ձեր հավելվածին արգելված է կատարել, լուռ բաց կթողնի գրառումները։ Համեմատեք ցանկը փաթեթների կառավարչի իրական վերադարձրածի հետ նախքան դրան վստահելը։

Աշխատանքային պրոֆիլները կրկին բաժանում են լսարանը։ Պրոֆիլի ներսում մեկնարկած թունելը ծածկում է այդ պրոֆիլը և նրա հավելվածները, մինչդեռ անձնական կողմը պահում է իր կապը։ Դա մեկ անգամ ընդունված շրջանակի որոշում է, և դրա տեղը ինտերֆեյսում է, ոչ թե աջակցության տոմսում։

Առասպել 4. stopSelf-ը վերջացնում է թունելը

Ոչ։ Ինտերֆեյսն ապրում է այն դեսկրիպտորում, որը վերադարձնում է establish()-ը, և ծառայության օբյեկտը կարող է մահանալ, քանի դեռ այդ դեսկրիպտորը բաց է մնում։ Նախ փակեք ParcelFileDescriptor-ը, բաց թողեք հղումը, միացեք ընթերցող և գրող թելերին կամ ընդհատեք դրանք, ապա կանչեք stopForeground(true)-ը և stopSelf()-ը այդ հերթականությամբ։

Մոռացված դեսկրիպտորը սովորական պատճառն է, թե ինչու ծառայությունը թվում է, թե վերապրում է իր իսկ կանգնեցման հարցումը, և դա բացատրում է հնացած երթուղին, որն ապրում է ավելի երկար, քան այն ավելացրած հավելվածը։

Հակառակ սխալը նույնքան տարածված է. փակել դեսկրիպտորը, երբ ընթերցող թելը ընթերցման կեսին է, ապա թողնել այդ թելին զբաղվել բացառությամբ, որի համար ոչ ոք մշակում չի գրել։ Պահեք անջատման հաջորդականությունը մեկ տեղում և թողեք թե օգտատիրոջ գործողությունը, թե onRevoke()-ը կանչեն դրա մեջ։

Ինչ ստուգել, երբ թունելը մեռած է թվում

Պաշտպանեք երկարատև սոքեթները protect()-ով նախքան թունելը բարձրանալը։ Առանց դրա թունելի սեփական տրանսպորտը կրկին երթուղավորվում է թունելի ներս, և կապը կանգ է առնում այնպես, որ նույնական է սերվերի խափանմանը։ Փորձարկեք մի հասցեի դեմ, որը գոյություն ունի միայն հեռավոր ցանցի հետևում, ոչ թե հանրային կայքի դեմ, որը պատասխանում է քեշից կամ հիմնական ուղուց։

Գրանցեք establish()-ի արդյունքը, երթուղիները, բացառված փաթեթները և անջատման պատճառը։ Այդ չորս տողերը առանձնացնում են համաձայնության խնդիրը շրջանակի խնդրից և կենսացիկլի խնդրից՝ առանց փաթեթների որսման։

Առնչվող հոդվածներ