addDnsServer Bad Address: Android thực sự kiểm tra điều gì
Lỗi Bad address từ addDnsServer là kiểm tra định dạng cục bộ, không phải sự cố DNS. Phân tích literal, loại bỏ loopback và wildcard, rồi kiểm tra khả năng kết nối.
Giá trị đến VpnService.Builder.addDnsServer dưới dạng chuỗi và builder từ chối ngay tại đó trước khi bất cứ thứ gì được thiết lập. Việc từ chối này là cục bộ: không resolver nào được liên hệ, không có tunnel, và thứ thất bại là kiểm tra định dạng, không phải DNS.
Điều này quan trọng vì một ứng dụng VPN điển hình có ba đầu vào DNS giống nhau: giá trị trong hồ sơ, giá trị máy chủ gửi và Private DNS ở cấp hệ thống. Chỉ hai đầu tiên đến được lời gọi này.
Đối tượng chuẩn bị kiểm tra đối số ngay lập tức
VpnService.Builder không phải tunnel. Đó là đối tượng chuẩn bị tập hợp địa chỉ, tuyến và máy chủ, và chỉ establish() biến các danh sách đó thành ParcelFileDescriptor sống và giao diện. Mục DNS được kiểm tra khi gọi addDnsServer.
Hai hệ quả: dấu vết ngắn và cố ý không có thông tin, và catch cẩu thả để lại tunnel không có resolver.
Builder giữ mọi danh sách được đưa; thêm một máy chủ vượt qua kiểm tra chỉ chứng minh chuỗi đã được phân tích như literal địa chỉ lúc dựng.
Đối số có thể là gì
Tham số là địa chỉ, không phải endpoint. Tài liệu Builder nhận literal IPv4 hoặc IPv6, và phân tích chỉ là số: android.net.InetAddresses.parseNumericAddress là bộ mặt công khai của parser đó từ API 29.
Ánh xạ thực tế như sau:
| Input | Result | Reason |
|---|---|---|
10.8.0.1 | accepted | IPv4 literal |
2606:4700:4700::1111 | accepted | IPv6 literal |
dns.example.com | rejected | hostname; numeric parsing does not resolve it |
10.8.0.1:53 | rejected | port belongs to your own config model |
https://dns.example.com/dns-query | rejected | DoH URL, a different configuration path |
127.0.0.1 | rejected | loopback is not a remote resolver |
0.0.0.0 or :: | accepted by the builder | parses fine, but names no destination; only your own validator rejects it |
Phiên bản ảnh hưởng đến cách bạn xác thực, không phải thứ builder chấp nhận. InetAddresses có từ API 29, nên trên Android 10 phân tích là một lời gọi; addDnsServer(InetAddress) là dạng gốc, String là phần bổ sung mới hơn. Dưới API 29 InetAddress.getByName sẽ phân giải tên máy chủ bằng truy vấn mạng chặn.
Loopback bị từ chối ở đây, wildcard thì không
Ứng dụng VPN trên máy tính thường trỏ stub resolver của họ tới 127.0.0.1:53; sao chép điều đó vào hồ sơ Android đưa vào một giá trị mô tả máy cục bộ, và ở đây không có máy cục bộ. Builder chỉ từ chối trường hợp này: địa chỉ null hoặc loopback ném IllegalArgumentException.
Wildcard là trường hợp ngược lại và đây là nơi phần lớn cách đọc sai. 0.0.0.0 và :: không phải loopback, nên builder chấp nhận chúng; không có gì bị ném lúc dựng. Giao diện sau đó lên với một mục không nêu đích đến. Bắt được điều đó là việc của validator: coi isAnyLocalAddress là đầu vào không hợp lệ.
Phân tích một lần tại ranh giới cấu hình
Cách sửa rẻ là xác thực nơi chuỗi đi vào mô hình của bạn:
import android.net.InetAddresses
import java.net.InetAddress
private fun dnsLiteralOrNull(raw: String?): InetAddress? {
val text = raw?.trim().orEmpty()
if (text.isEmpty()) return null
val address = try {
InetAddresses.parseNumericAddress(text)
} catch (e: IllegalArgumentException) {
return null
}
return address.takeUnless { it.isAnyLocalAddress || it.isLoopbackAddress }
}
Kết quả null trở thành lỗi cấp trường mang văn bản sai, và đó là dạng duy nhất hỗ trợ có thể xử lý. Trên đường thành công, đối tượng đi thẳng vào builder.addDnsServer(address), và bắt IllegalArgumentException cụ thể, không phải Exception.
Đừng thay bằng resolver công cộng khi giá trị mong muốn bị từ chối; hồ sơ âm thầm đổi sang DNS công cộng vẫn kết nối, nhưng tên nội bộ không còn phân giải.
Xác nhận khả năng kết nối sau khi giao diện tồn tại
Chấp nhận của builder là điểm kiểm tra đầu tiên, không phải cuối cùng. Khi giao diện lên, LinkProperties.getDnsServers() báo cáo điều mạng thực sự nhận, và adb shell dumpsys connectivity in các thuộc tính đó từ bên ngoài.
Private DNS là điều khiển riêng và không đi qua đối số này; tên máy chủ của nhà cung cấp DoT thuộc cài đặt hệ thống, không phải trường IP.
Trước khi đổi parser hồ sơ lần nữa, ghi lại chuỗi thô và nguồn của nó, xác nhận đó là literal chứ không phải tên máy chủ, không phải loopback hay wildcard, và chuyển đối tượng đã phân tích.