Android VpnService-ը միանում է, բայց ոչ մի փաթեթ ցանց չի հասնում
Թունելը բարձրացված է, էջերը դեռ ձախողվում են. ստուգեք կարդալու ռեժիմը, socket-ի պաշտպանությունը, TCP աջակցությունը և երթուղիների ցանկը, նախքան VPN սերվերին մեղադրելը։
Թունելը բարձրանում է, բանալու պատկերակը հայտնվում է, և բրաուզերի յուրաքանչյուր հարցում դեռ ժամկետանց է լինում։ VpnService հավելվածում այդ համակցությունը սովորաբար նշանակում է, որ կարդալու ցիկլը կենդանի է, իսկ ելքի կեսը կոտրված, ոչ թե սերվերը մերժել է ձեռքսեղմումը։ Android-ն արդեն ստեղծել է ինտերֆեյսը, տեղափոխել հավելվածի թրաֆիկը դրա մեջ և ձեր գործընթացին տվել ֆայլի նկարագրիչ։ Այդ նկարագրիչից հետո ամեն ինչ ձեր կոդն է, և մեկ բաց թողնված կանչը բավական է ամբողջ հոսքը կուլ տալու համար։
Ինչ է տալիս համակարգը և ինչ չի տալիս
establish()-ը վերադարձնում է ParcelFileDescriptor tun ինտերֆեյսի համար։ Համակարգը դրա մեջ գրում է ելքային IP փաթեթներ և ակնկալում, որ ձեր գործընթացը կկարդա դրանք, կորոշի, թե ուր են գնում, և կուղարկի սովորական socket-ով։ Դրա տակ միջուկային փոխանցում չկա, ուստի երբ ոչ ոք չի կարդում, փաթեթները դեն են նետվում, և ինտերֆեյսի հաշվիչները երբեք չեն շարժվում։
Սկսեք հաշվելով, ոչ թե գուշակելով։ Ամեն հաջող ընթերցման ժամանակ ավելացրեք հաշվիչ և գրանցեք առաջին փաթեթների նպատակակետն ու արձանագրությունը։ Եթե հաշվիչը զրո է մնում, մինչ թրաֆիկ է առաջանում, սխալը ձեր փոխանցման կոդից վերև է, ինչը որոնումը սահմանափակում է builder-ի կարգավորմամբ և կարդալու ռեժիմով։ Եթե հաշվիչը շարժվում է, բայց բրաուզերը դեռ ձախողվում է, սխալը ուղարկելու կամ վերադարձի ուղու մեջ է։
Բլոկող ընթերցումը որոշում է՝ ցիկլը շարժվում է, թե ոչ
setBlocking(true)-ն նկարագրիչը դնում է բլոկող ռեժիմի և գոյություն ունի API 21-ից։ Հին տարբերակներում լուծումը փոքր JNI օգնականն է, որը fcntl-ով մաքրում է O_NONBLOCK-ը հում նկարագրիչի վրա։ Ռեժիմը կարևոր է, քանի որ որոշում է, թե ինչ կարող է անել սպասող փաթեթ չունեցող ընթերցումը։
Ոչ բլոկող ռեժիմում այդ ընթերցումը անմիջապես վերադառնում է բացասական արժեքով, և արդյունքն անտեսող ցիկլը պտտում է միջուկը՝ ոչ մի թրաֆիկ չշարժելով։ Բլոկող ռեժիմում ընթերցումը սպասում է, ինչը փոխանցող թելն է ուզում, բայց այդ ժամանակ թելն այլևս հնարավոր չէ ընդհատել։ Նկարագրիչը փակելը ընթերցումը ձախողելու աջակցվող եղանակն է, որպեսզի ցիկլը դուրս գա։ Գործընթացը սպանելը կամ թելը ընդհատելը կարող է հաջորդ մեկնարկին թողնել նկարագրիչ, որը այլևս չի համապատասխանում ինտերֆեյսին։
Կարդացեք մեկ փաթեթ մեկ կանչի համար՝ ինտերֆեյսի MTU չափի բուֆերի մեջ, և զրոն կամ բացասական վերադարձը դիտարկեք որպես սխալ, ոչ թե դատարկ փաթեթ։
protect() առաջին ուղարկումից առաջ, ոչ հետո
Ինկապսուլացված փաթեթ կրող յուրաքանչյուր socket նախ պետք է պաշտպանվի։ Չպաշտպանված socket-ը ենթարկվում է հենց նոր տեղադրած երթուղային կանոններին, ուստի նրա փաթեթները վերադառնում են թունել, և ձեր սեփական ընթերցման ցիկլը նորից վերցնում է դրանք։ Այնուհետև թունելն անվերջ փոխանցում է իր ելքը, և դրսից դա նման է բացվող և երբեք չպատասխանող կապի։
Հերթականությունը՝ protect, ապա connect կամ bind, ապա send։ connect-ից հետո protect կանչելը կարող է թողնել նախորդ երթուղային որոշումը, ուստի socket-ը շարունակում է օգտագործել սխալ ուղին, թեև կանչը վերադարձրել է true։ Ստուգեք վերադարձվող բուլյան արժեքը և բարձր ձախողվեք, երբ այն false է, փոխարենը մտնելու այնպիսի ուղարկման մեջ, որը չի կարող աշխատել։
sendto-ից եկած թույլտվության սխալը socket-ի վրա, որը երբեք չի դիպչել protect()-ին, այս սխալի սովորական նշանն է։
UDP օրինակը HTTPS չի տեղափոխում
Փոխանցման օրինակը, որը պատճենում են դասագրքերի մեծ մասը, զբաղվում է միայն UDP-ով։ TCP-ին պետք են ձեռքսեղմում, հաջորդականության համարներ, հաստատումներ և կրկնակի ուղարկում, և ցիկլը, որը պատճենում է բեռը tun նկարագրիչի և socket-ի միջև, դրանցից ոչ մեկը չի տալիս։ Այդ պատճառով UDP DNS հարցումը կարող է անցնել, մինչ յուրաքանչյուր HTTPS հարցում կանգ է առնում։ Կա՛մ գործարկեք TCP կույտ օգտագործողի տարածքում, կա՛մ կապերը հանձնեք տեղական պրոքսիի, որը սարքում ավարտում է TCP-ն և փոխանցում հոսքը։
Փորձարկեք բաժանումը նախքան որևէ բան վերաշարադրելը. ուղարկեք մեկ UDP հարցում և բացեք մեկ TCP կապ նույն հոսթի հետ։ Եթե UDP ուղին աշխատում է, իսկ TCP ուղին կախվում է առաջին SYN-ի վրա, խնդիրը փոխանցման կոդում է, ոչ սերվերում։ Այդ մեկ համեմատությունը խնայում է շատ ժամանակ, որը վատնվում է անօգուտ սերվերի մատյաններ կարդալու վրա։
Երթուղիների ցանկը և հավելվածային զտիչները սահմանափակում են, թե ինչ եք տեսնում
addAddress, addRoute և addDns-ը որոշում են, թե ինչ է համակարգն ուղարկում թունել։ Երթուղիների ցանկից դուրս նպատակակետը երբեք չի հասնում ձեր նկարագրիչին, ուստի փոխանցման ոչ մի քանակ չի ուղղի դա։ Գրանցեք ձեր կատարած builder կանչերը և համեմատեք դրանք այն հասցեների հետ, որոնց հավելվածն իսկապես փորձում է հասնել։
Հավելվածային ընտրությունն ունի իր ստորին սահմանը. addAllowedApplication-ը և addDisallowedApplication-ը պահանջում են API 21, իսկ հին տարբերակներում այդ մեթոդները պարզապես բացակայում են։ Փաթեթով զտող ձևավորումը այնտեղ ընդհանրապես չի կարող աշխատել։
Վերականգնումը թողնում է հնացած նկարագրիչներ
establish()-ի կրկնակի կանչը վերադարձնում է նոր նկարագրիչ և փակում նախորդ ինտերֆեյսը։ Հին նկարագրիչն այժմ անվավեր է, ուստի դրա վրա դեռ բլոկված թելը սխալ է ստանում, և հին ուղու համար ստեղծված socket-ները կարող են փոխարինման կարիք ունենալ։ onRevoke-ը հետկանչն է այն թունելի համար, որը վերցրել են օգտատերը կամ համակարգը։ Կյանքի ցիկլը մեկ սեփականատիրոջ մոտ պահելը, որը կանգնեցնում է ընթերցողին վերականգնումից առաջ, կանխում է միացած-բայց-մեռած վիճակների մեծ մասը։
Կարդացեք յուրաքանչյուր սխալ՝ ըստ այն քայլի, որին այն պատկանում է։ recvfrom-ից կապի վերակայումը նշանակում է, որ հեռավոր ծայրը փակվել է, մինչ ձեր կոդը դեռ կարդում էր։ Վատ հասցեի սխալը ցույց է տալիս վերնագրի վերլուծությանը կամ offset-ի սխալին, ոչ թե ցանցին։
Աշխատանքի հերթականությունը. բլոկող ռեժիմը միացված, մեկ փաթեթ մեկ ընթերցման համար, վերնագիրը վերլուծված, socket-ը պաշտպանված, բեռն ուղարկված, մաքուր փակում հանձնման ժամանակ և նպատակակետը ծածկող երթուղիների ցանկ։