Đường hầm dựng lên, biểu tượng chìa khóa hiện ra, nhưng mọi yêu cầu trong trình duyệt vẫn hết thời gian chờ. Trong ứng dụng VpnService, sự kết hợp đó thường có nghĩa là vòng lặp đọc vẫn sống và nửa đầu ra bị hỏng, chứ không phải máy chủ từ chối bắt tay. Android đã tạo giao diện, chuyển lưu lượng của ứng dụng vào đó và giao cho tiến trình của bạn một bộ mô tả tệp. Mọi thứ sau bộ mô tả đó là mã của bạn, và một lời gọi thiếu là đủ để nuốt toàn bộ luồng.

Hệ thống cho gì và không cho gì

establish() trả về ParcelFileDescriptor cho giao diện tun. Hệ thống ghi các gói IP đi ra vào đó và mong tiến trình của bạn đọc chúng, quyết định chúng đi đâu và gửi qua một socket thông thường. Bên dưới không có chuyển tiếp trong nhân, nên khi không ai đọc, các gói bị bỏ và bộ đếm của giao diện không bao giờ nhúc nhích.

Hãy bắt đầu bằng việc đếm thay vì đoán. Tăng bộ đếm mỗi lần đọc thành công và ghi lại đích đến cùng giao thức của những gói đầu tiên. Nếu bộ đếm vẫn bằng không trong khi lưu lượng được tạo ra, lỗi nằm phía trên mã chuyển tiếp của bạn, điều này thu hẹp tìm kiếm vào cấu hình builder và chế độ đọc. Nếu bộ đếm nhảy nhưng trình duyệt vẫn lỗi, lỗi nằm trên đường gửi hoặc đường về.

Đọc chặn quyết định vòng lặp có tiến hay không

setBlocking(true) đặt bộ mô tả vào chế độ chặn và có từ API 21. Trên các bản phát hành cũ, cách khắc phục là một trợ giúp JNI nhỏ xóa O_NONBLOCK bằng fcntl trên bộ mô tả thô. Chế độ quan trọng vì nó quyết định một lần đọc không có gói chờ được phép làm gì.

Ở chế độ không chặn, lần đọc đó trả về ngay với giá trị âm, và vòng lặp bỏ qua kết quả sẽ quay một lõi mà không di chuyển lưu lượng nào. Ở chế độ chặn, lần đọc chờ, đúng điều một luồng chuyển tiếp muốn, nhưng sau đó không thể ngắt luồng nữa. Đóng bộ mô tả là cách được hỗ trợ để làm lần đọc thất bại để vòng lặp có thể thoát. Giết tiến trình hoặc ngắt luồng thay vào đó có thể để lần khởi động sau giữ một bộ mô tả không còn khớp với giao diện.

Đọc một gói mỗi lời gọi vào bộ đệm có kích thước bằng MTU của giao diện, và coi số không hoặc giá trị trả về âm là lỗi chứ không phải gói rỗng.

protect() trước lần gửi đầu tiên, không phải sau

Mọi socket mang gói đã đóng gói phải được bảo vệ trước. Một socket không được bảo vệ tuân theo đúng các quy tắc định tuyến bạn vừa cài, nên các gói của nó quay lại đường hầm và vòng lặp đọc của chính bạn lại nhặt chúng. Đường hầm sau đó chuyển tiếp đầu ra của chính nó vô tận, và từ bên ngoài trông như một kết nối mở ra mà không bao giờ trả lời.

Thứ tự là protect, rồi connect hoặc bind, rồi send. Gọi protect sau connect có thể để quyết định tuyến trước đó nguyên vẹn, nên socket tiếp tục dùng sai đường dù lời gọi trả về true. Kiểm tra boolean trả về và thất bại ồn ào khi nó là false, thay vì bước vào một lần gửi không thể hoạt động.

Lỗi quyền từ sendto trên một socket chưa từng chạm protect() là dấu hiệu thường gặp của lỗi này.

Ví dụ UDP không mang HTTPS

Ví dụ chuyển tiếp mà hầu hết hướng dẫn sao chép chỉ xử lý UDP. TCP cần bắt tay, số thứ tự, xác nhận và truyền lại, và một vòng lặp sao chép tải giữa bộ mô tả tun và socket không cung cấp điều nào trong số đó. Vì vậy truy vấn DNS qua UDP có thể đi qua trong khi mọi yêu cầu HTTPS bị kẹt. Hoặc chạy ngăn xếp TCP trong không gian người dùng hoặc giao kết nối cho một proxy cục bộ kết thúc TCP trên thiết bị và chuyển tiếp luồng.

Kiểm tra sự phân tách trước khi viết lại bất cứ thứ gì: gửi một truy vấn UDP và mở một kết nối TCP tới cùng máy chủ. Nếu đường UDP hoạt động và đường TCP kẹt ở SYN đầu tiên, mã chuyển tiếp là vấn đề chứ không phải máy chủ. Một phép so sánh đó tiết kiệm rất nhiều thời gian đọc nhật ký máy chủ không có gì hữu ích.

Danh sách tuyến và bộ lọc theo ứng dụng giới hạn những gì bạn thấy

addAddress, addRoute và addDns xác định hệ thống gửi gì vào đường hầm. Một đích ngoài danh sách tuyến không bao giờ tới bộ mô tả của bạn, nên không lượng logic chuyển tiếp nào sửa được. Ghi lại các lời gọi builder bạn thực hiện và so sánh với các địa chỉ mà ứng dụng thực sự cố gắng tiếp cận.

Lựa chọn theo ứng dụng có ngưỡng riêng: addAllowedApplication và addDisallowedApplication cần API 21, và trên các bản cũ các phương thức đó đơn giản là không có. Một thiết kế lọc theo gói không thể chạy ở đó.

Thiết lập lại để lại các bộ mô tả cũ

Một lời gọi establish() mới trả về bộ mô tả mới và đóng giao diện trước. Bộ mô tả cũ giờ không hợp lệ, nên một luồng vẫn bị chặn trên nó nhận lỗi, và các socket tạo cho đường cũ có thể cần được thay. onRevoke là callback cho đường hầm mà người dùng hoặc hệ thống đã lấy đi. Giữ vòng đời ở một chủ sở hữu, người dừng trình đọc trước khi thiết lập lại, ngăn phần lớn các trạng thái đã kết nối nhưng chết.

Đọc từng lỗi theo bước mà nó thuộc về. Đặt lại kết nối từ recvfrom nghĩa là đầu xa đã đóng trong khi mã của bạn vẫn đang đọc. Lỗi địa chỉ sai chỉ vào việc phân tích tiêu đề hoặc lỗi độ lệch, không phải mạng.

Thứ tự công việc: bật chế độ chặn, một gói mỗi lần đọc, tiêu đề đã phân tích, socket được bảo vệ, tải đã gửi, đóng sạch khi bàn giao và danh sách tuyến bao phủ đích.

Bài viết liên quan