არა, VpnService აპს ტელეფონის VPN-ის ჩართვა არ შეუძლია
აპი ფლობს ერთ გვირაბს და მის სასიცოცხლო ციკლს, და არა ტელეფონის VPN ჩამრთველს. თანხმობა შეიძლება გაუქმდეს, ერთდროულად მხოლოდ ერთი VPN მუშაობს, და პაკეტების ანალიზი თქვენ გრჩებათ.
მცდარი ვარაუდია, რომ VpnService აპი სისტემურ ჩამრთველს ატრიალებს და ამის შემდეგ ტელეფონი დაცულია. სინამდვილეში უფრო ვიწრო რამ ხდება: აპი თანხმობას ითხოვს, აწყობს ინტერფეისს, ფაილის დესკრიპტორის მეშვეობით კითხულობს და წერს პაკეტებს, და შეჩერებისას ინტერფეისს აბრუნებს. ერთდროულად მხოლოდ ერთი ასეთი გვირაბი შეიძლება იყოს ცოცხალი, და სისტემას შეუძლია ის კითხვის გარეშე წაიღოს. ხალხი პროტოკოლს რომ დაბრალებს დამაბნეველი ქცევის უმეტესობა სწორედ ამ საკუთრების მოდელიდან მოდის.
მოეპყარით სასიცოცხლო ციკლის ყოველ გადასვლას დიზაინის ნაწილად და არა კიდურა შემთხვევად.
მითი: აპი ტელეფონის VPN-ს რთავს
ნაკადი იწყება VpnService.prepare()-ით, რომელიც აბრუნებს Intent-ს, რომელიც მომხმარებელმა უნდა მიიღოს, და გრძელდება establish()-ით, რომელიც შემდეგ მაინც შეიძლება ჩავარდეს იმ მიზეზების გამო, რომლებსაც აპი არ აკონტროლებს. მეორე გვირაბის დადგენა გამორთავს პირველ ინტერფეისს და ცვლის სისტემურ შეტყობინებას. ამ თანმიმდევრობის არც ერთ წერტილში აპს არ ფლობს მთელი მოწყობილობის ჩამრთველს; მას ეკუთვნის მისთვის მიცემული დესკრიპტორი და პაკეტები, რომლებიც მასზე ჯდება. თვით მუდმივი შეტყობინებაც სისტემას ეკუთვნის და არა აპის დიზაინს.
რადგან ინტერფეისი მიეცემა და არ იტაცება, სხვა კლიენტს შეუძლია ადგილი ნებისმიერ დროს დაიკავოს. აპი ამას იგებს უკუგამოძახებით და არა დაბრუნებული მნიშვნელობით.
აპის იძულებით შეჩერება შეტყობინებების პანელში სწორედ ამიტომ ჩანს: სისტემა აცხადებს, რა მისცა და არა ის, რისი ჩვენებაც აპმა აირჩია.
მითი: თანხმობა ერთხელ მიეცემა
როცა სისტემა გვირაბს სხვა კლიენტს გადასცემს, ის გაუქმების ტრანზაქციას უგზავნის იმ binder-ს, რომელსაც საბაზო კლასი onBind()-იდან აბრუნებს. აპმა, რომელიც ამ binder-ს საკუთარით ცვლის, გაუქმების ტრანზაქცია თავად უნდა გადაამისამართოს, თორემ VpnService.onRevoke() არასოდეს ამოქმედდება და სერვისი განაგრძობს წერას დესკრიპტორში, რომელიც აღარ არსებობს. თანხმობა მომხმარებელსაც შეუძლია გააუქმოს, და სისტემას შეუძლია სერვისი შეაჩეროს გადატვირთვის ან ყოველთვის ჩართული ხელახალი დაკავშირების დროს.
ამასთან გამკლავება კონკრეტული სამუშაოა და არა ლოგის ხაზი: შეწყვიტეთ დესკრიპტორის კითხვა, დახურეთ ის, გაასუფთავეთ დაკავშირების დროშა და უთხარით მომხმარებელს, რომელ მდგომარეობაშია ის. შემდეგ გადაწყვიტეთ, აპმა უნდა ხელახლა დაადგინოს, კვლავ იკითხოს თუ დაელოდოს, რადგან ყოველთვის ჩართულ კონფიგურაციაში სისტემამ შეიძლება სერვისი თავად დააბრუნოს.
აპი, რომელიც გაუქმებას გამონაკლისად და არა მდგომარეობად განიხილავს, დააფიქსირებს დაკავშირებას მაშინ, როცა მის მიერ დაწერილი ყოველი პაკეტი გადაიყრება, და ეს კონფიდენციალურობის ხელსაწყოსთვის ყველაზე ცუდი წარუმატებლობის რეჟიმია.
მითი: ინტერფეისი ტრაფიკს თქვენთვის ფილტრავს
რაც მოდის, ნედლი IP-ა. დაჭერა ან ფილტრაცია ნიშნავს IP, TCP, UDP და IPv6 სათაურების ანალიზს, ნაკადების თვალყურის დევნებას და პასუხების დესკრიპტორში უკან ჩაწერას, რაც დამჭერ აპს პატარა ქსელურ დასტად აქცევს და არა სისტემურ საშუალებად. protect() არსებობს იმისთვის, რომ თავად აპის სატრანსპორტო სოკეტმა გვერდი აუაროს თავის გვირაბს და არ დაიჭიროს საკუთარი თავი, და ის აბრუნებს ლოგიკურ მნიშვნელობას, რომელიც შემოწმებას იმსახურებს გვირაბის აწევის შემდეგ შექმნილ ყოველ სოკეტზე.
აწყობის დაშვებული და აკრძალული აპების სიები განსაზღვრავს, ვისი ტრაფიკი შედის ინტერფეისში. ისინი არ აძლევენ წვდომას ინტერფეისის მიერ ტარებული IP პაკეტების მიღმა არსებულ დატვირთვაზე, და არც ერთი API არ ამჟღავნებს სხვა კლიენტის გვირაბს ან ჩამოთვლის სისტემის VPN პროფილებს. ქსელის შესაძლებლობებს შეუძლია თქვას, რომ ქსელი VPN ტრანსპორტია, მაგრამ არა ის, თუ ვისი გვირაბია.
ამიტომ სხვა აპებზე მდგომარეობის კითხვები წინააღმდეგობრივ პასუხებს იღებენ: ინფორმაცია უბრალოდ თქვენს პროცესს არ ემჟღავნება.
რას ითხოვს სასიცოცხლო ციკლი სინამდვილეში
თვალყური ადევნეთ თქვენ მიერ კონტროლირებულ მდგომარეობებს: მომზადებული, დადგენილი, გაუქმებული, შეჩერებული. ხელახალი დაკავშირება უნდა იყოს დაშლა და შემდეგ დადგენა, დესკრიპტორისა და სატრანსპორტო სოკეტის ერთი მფლობელით, რადგან ორი კომპონენტი, რომლებიც დადგენაზე იშლიან, იბრძოლებენ ერთადერთ თავისუფალ ადგილზე. თანამედროვე Android-ზე ფონური გაშვებები შეზღუდულია, ამიტომ ჩატვირთვის გადაცემა გარანტია არ არის; გამოიყენეთ წინა პლანის სერვისი მისი შეტყობინებით იქ, სადაც API დონე იძლევა საშუალებას.
დაალოგეთ გადასვლა და არა მხოლოდ საბოლოო მდგომარეობა, და შეინახეთ მდგომარეობათა მანქანა საკმარისად პატარა, რომ ყოველი გზა შემოწმდეს. მდგომარეობა, რომელსაც ტესტში ვერ მიაღწევთ, ის მდგომარეობაა, რომელსაც ველზე ლოგის გარეშე შეხვდებით.
შეამოწმეთ სასიცოცხლო ციკლი მოწყობილობაზე
ემულატორებს შეუძლიათ კავშირის გაყალბება, მაგრამ თანხმობისა და გაუქმების გზის დამაჯერებლად არა, ამიტომ ეს გაუშვით რეალურ მოწყობილობაზე, სადაც ადგილის მოსაპარად მეორე VPN კლიენტია დაყენებული.
adb shell dumpsys connectivity | grep -i -A3 vpn
adb shell dumpsys activity services | grep -i -A2 vpnservice
adb logcat -s VpnService:V AndroidRuntime:E
adb shell am force-stop com.example.vpnapp
გაუშვით გვირაბი, დაადასტურეთ შეტყობინება და ინტერფეისი, შემდეგ შეაერთეთ მეორე კლიენტი და დააკვირდით, მუშაობს თუ არა გაუქმების უკუგამოძახება და იწმინდება თუ არა მდგომარეობა. იძულებით შეაჩერეთ აპი და შეამოწმეთ, იტყობინება თუ არა რამე ჯერ კიდევ ცოცხალ დესკრიპტორზე.
გაიმეორეთ თანმიმდევრობა ყოველთვის ჩართული გადატვირთვის შემდეგ, რადგან ეს გზა განსხვავდება მომხმარებლის მიერ დაწყებული კავშირისგან. თქვენ ეძებთ არა მწვანე ხატულას, არამედ მტკიცებულებას, რომ აპმა შეწყვიტა იმ დესკრიპტორის გამოყენება, რომელიც აღარ ეკუთვნოდა.