ოთხმა ვარაუდმა 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()-ის შედეგი, მარშრუტები, გამორიცხული პაკეტები და გამორთვის მიზეზი. ეს ოთხი ხაზი ერთმანეთისგან ყოფს თანხმობის პრობლემას არეის პრობლემისგან და სასიცოცხლო ციკლის პრობლემისგან პაკეტების დაჭერის გარეშე.

დაკავშირებული სტატიები