გვირაბი ადის, გასაღების ხატულა ჩნდება და ბრაუზერის ყოველი მოთხოვნა მაინც დროში იწურება. 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 დააბრუნა. შეამოწმეთ დაბრუნებული boolean და ხმამაღლა ჩავარდით, როცა ის 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 დაცული, დატვირთვა გაგზავნილი, სუფთა დახურვა გადაცემისას და მარშრუტების სია, რომელიც ფარავს დანიშნულებას.

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