Android VpnService: TUN-ის კითხვის გაჭედვა ENOBUFS-ის ჩაწერის შეცდომების წინააღმდეგ
კითხვა, რომელიც არასდროს ბრუნდება, და ჩაწერა, რომელიც ENOBUFS-ით ვარდება, საწინააღმდეგო მიზეზები აქვთ. გაზომეთ გამოცხადებული ფანჯარა და პაკეტის სიგრძე, რომ აირჩიოთ შესწორება.
ერთსა და იმავე VpnService ციკლში ორი სიმპტომი ჩნდება და ორივეს ერთი და იმავე რამ ედება ბრალი: მკითხველი, რომელიც არასდროს ბრუნდება, და ჩაწერა, რომელიც ENOBUFS-ით ვარდება. ეს ერთი ხარვეზის ვარიანტები არ არის. გაჭედილი კითხვა პარტნიორთან ნაკადის კონტროლზე უთანხმოებაა; წარუმატებელი ჩაწერა პაკეტია, რომელსაც ინტერფეისი არ მიიღებს. თუ მათ ერთ პრობლემად განიხილავთ, დღეს დახარჯავთ იმ ბუფერების რეგულირებაზე, რომლებიც არასდროს ყოფილან ჩართული. ქვემოთ მოცემული გაყოფა არის ის, როგორც მე ვწყვეტ, ციკლის რომელი ნახევარი გავზომო პირველად.
ერთი ციკლი, ორი დამოუკიდებელი მარცხის წერტილი
თქვენი სერვისი IP პაკეტს კითხულობს TUN ფაილის დესკრიპტორიდან, ამოწმებს მას და პასუხს წერს იმავე დესკრიპტორში. ყველაფერი, რაც ამ გზაზე ფუჭდება, ამ ორი გამოძახებიდან ერთ-ერთში ვლინდება, ამიტომ ორივეს გატეხილ გვირაბად აცხადებენ. გამოძახებებს სხვადასხვა კონტრაქტი აქვთ. კითხვა ელოდება, რომ ბირთვმა პაკეტი მოგცეთ, ხოლო ჩაწერა ბირთვს გადასცემს პაკეტს, რომლის უარყოფის უფლებაც მას აქვს.
კითხვა არის ის ადვილი ნახევარი, რომელზეც ფიქრი შეიძლება. კითხვა, რომელიც არაფერს აბრუნებს, ან ელოდება ტრაფიკს, რომელიც არასდროს მოდის, ან ელოდება იმიტომ, რომ პარტნიორმა გაგზავნა შეწყვიტა.
გაჭედილი კითხვა ნაკადის კონტროლის უთანხმოებაა
TUN მოწყობილობის კითხვა ბლოკავს მანამ, სანამ პაკეტი ხელმისაწვდომი არ გახდება. ეს დაპროექტებული ქცევაა და ამიტომ read-ში მდგარი ნაკადი უსაქმურ კავშირზე ნორმალურად გამოიყურება. ის ჩამოკიდებად იქცევა მაშინ, როცა თქვენი TCP სტეკი აგრძელებს ლოკალური აპიდან მონაცემების მიღებას, მაგრამ არასდროს აცხადებს საკმარის ფანჯარას, რომ დისტანციურმა პარტნიორმა მეტი გამოგზავნოს. პარტნიორი ავსებს თქვენს მიერ გამოცხადებულ ფანჯარას, ელოდება ACK-სა და ფანჯრის განახლებას და ჩერდება. მაშინ თქვენს მკითხველს დასაბრუნებელი აღარაფერი აქვს, და აპი ხედავს ღია და ჩუმ კავშირს.
გავრცელებული მიზეზებია სტეკი, რომელიც სოკეტში წერს გამოცხადებული ფანჯრის თვალყურის დევნების გარეშე, და ის, რომელიც ივიწყებს ფანჯრის განახლების გაგზავნას ბუფერის დაცლის შემდეგ. გარედან ისინი ერთნაირად გამოიყურებიან: ბაიტები გადის, არაფერი ბრუნდება, შეცდომა არ აღიქვამება. მკითხველი ნაკადის დაბლოკვა პარსინგზე, DNS გადაწყვეტაზე ან დისკის I/O-ზე იწვევს იმავე ფორმის სიმპტომს დაუკავშირებელი მიზეზით, ამიტომ სანამ ნაკადის კონტროლს დაადანაშაულებთ, დაადასტურეთ, რომ ნაკადი ნამდვილად read-შია.
შეამოწმეთ პარტნიორის გაგზავნის რიგი და ფანჯარა, რომელიც მან ბოლოს ნახა. თუ პარტნიორი მონაცემებს იკავებს იმიტომ, რომ თქვენი ფანჯარა ნულია, შესწორება თქვენს ACK გზაშია და არა TUN-ის კითხვაში.
ENOBUFS ზომის პრობლემაა და არა ბუფერის ნაკლებობა
ჩაწერა, რომელიც TUN ინტერფეისზე ENOBUFS-ს აბრუნებს, ჩვეულებრივ პაკეტის სიგრძეს უარყოფს. ინტერფეისს აქვს MTU, მასზე დიდი პაკეტი რიგში ვერ ჩადგება, და იმავე პაკეტის ხელახალი ცდა ყოველ ჯერზე ერთნაირად ვარდება. ეს დეტერმინიზმია ნიშანი. ნამდვილი ბუფერის ნაკლებობა იწმინდება წნევის დაწევასთან ერთად, ხოლო ზომის უარყოფას არ აინტერესებს რამდენს დაელოდებით.
TCP-ს აქვს მექანიზმი ზუსტად ამისთვის და ეს არის ნაბიჯი, რომელსაც ხალხი გამოტოვებს: MSS შეზღუდვა. როცა მომხმარებლის სივრცეში სეგმენტებს ასინთეზებთ, დატვირთვის ზომას თქვენ ირჩევთ, და მისი ლოკალური ინტერფეისის MTU-დან აღება გვირაბის MTU-ის ნაცვლად გამოსცემს სეგმენტებს, რომლებსაც გვირაბი ვერ გადაიტანს. შეზღუდეთ ორიდან პატარაზე. შეზღუდვა მხოლოდ TCP-ს ფარავს, ამიტომ ზედმეტად დიდი UDP დატაგრამა თქვენმა კოდმა უნდა დაშალოს ან გადააგდოს.
ჩაწერეთ ყოველი პაკეტის სიგრძე, რომელსაც დებაგის დროს წერთ. სიგრძე, რომელიც ვარდება, პასუხია და ის ცდებს შორის არ შეიცვლება.
სამი გაზომვა, რომელიც შემთხვევებს ჰყოფს
ორივე მარცხისთვის დებაგერი არ გჭირდებათ. სამი დაკვირვება საკმარისია, რომ სწორ განყოფილებამდე მიაღწიოთ, და ისინი ხელახალ აწყობაზე ნაკლებ დროს იღებს.
| What you see | Where to look |
|---|---|
| Reader parked, peer send queue empty, last advertised window zero | Window updates in your ACK path |
| Write fails on one length and succeeds on a smaller one | MSS clamp and tunnel MTU |
| Both directions look fine, app still sees no data | Protected socket and routing loop |
თუ მესამე სტრიქონი ემთხვევა, თავად ციკლი რიგზეა და პრობლემა იქ არის, სადაც გვირაბი ვერ ხედავს: დაუცველი სატრანსპორტო სოკეტი, რომელიც თქვენს VPN ტრაფიკს უკან გვირაბში აბრუნებს, რაც ჩვეულებრივ ჩანს როგორც კავშირი, რომელიც წარმატდება და შემდეგ არაფერს აწვდის.
შეაკეთეთ მილსადენი, რომელზეც ორივე გზაა დამოკიდებული
არცერთი სიმპტომი არ ღირს დევნად, სანამ ინტერფეისი სწორად არ არის აწყობილი. მნიშვნელოვანი builder გამოძახებებია მისამართი, მარშრუტი, MTU და თანხმობა, ამ თანმიმდევრობით:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()დააყენეთ MTU გვირაბის რეალურ მნიშვნელობაზე, ნაცვლად იმისა, რომ დატოვოთ იმაზე, რაც მაგალითმა გამოიყენა. ყველაფერი, რასაც მოგვიანებით დაწერთ, ამ რიცხვით შემოიფარგლება, და შეუსაბამობა თქვენს მიერ დაყენებულ MTU-სა და იმ MSS-ს შორის, რომელზეც ზღუდავთ, ყველაზე გავრცელებული გზაა შესწორების შემდეგ ჩაწერის მარცხის დასაბრუნებლად. შემდეგ დაიცავით სოკეტი, რომელიც ტრაფიკს თავად VPN სერვერამდე მიჰყავს. protect-ის გარეშე ეს პაკეტები უკან თქვენსავე გვირაბში მარშრუტირდება და კავშირი არასდროს სრულდება, რაც გაჭედილ კითხვას ჰგავს, მაგრამ მარშრუტიზაციის ციკლია.
თანხმობის გვერდის ავლა შეუძლებელია. სერვისი, რომელიც იწყება prepare-ის მიერ null-ის დაბრუნების გარეშე, ინტერფეისს არ იღებს, და establish null-ს აბრუნებს გამონაკლისის გადაგდების ნაცვლად, ამიტომ ციკლის დაწყებამდე შეამოწმეთ დესკრიპტორი. VpnService-ის ცნობარი არის ადგილი, სადაც builder-ის კონტრაქტს დაადასტურებთ მაგალითის კოპირების ნაცვლად.
ერთდროულად ერთი მიმართულება შეამოწმეთ
TCP-ის შეხებამდე გაგზავნეთ UDP ექო გვირაბით და დააკვირდით პაკეტების სიგრძეებს. შემდეგ გახსენით ხელის ჩამორთმევა და დაადასტურეთ, რომ სეგმენტები თქვენს გამოთვლილ MSS-ზე დაბლა რჩებიან. დათვალეთ პაკეტები და არა კავშირები, რომ გაჭედვა ჩამოკიდებული სოკეტის ნაცვლად მრიცხველში ხარვეზად გამოჩნდეს. თუ ჩაწერების აფეთქების შემდეგ კითხვა ჩერდება, ეჭვმიტანილი ფანჯარაა; თუ ჩაწერა მაშინვე ვარდება, სიგრძე.
შემდეგ გადატვირთეთ მოწყობილობა და ერთხელ გაიმეორეთ, რადგან VpnService, რომელიც მხოლოდ ქსელის პირველ ცვლილებამდე მუშაობს, მარშრუტიზაციის პრობლემაა და არა ნაკადის კონტროლის.