agent-bigo/app/src/main/java/com/example/studyapp/proxy/CustomVpnService.java

287 lines
11 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.example.studyapp.proxy;
import android.content.Intent;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import com.example.studyapp.utils.V2rayUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
public class CustomVpnService extends VpnService {
private static final String TUN_ADDRESS = "172.19.0.1"; // TUN 的 IP 地址
private static final int PREFIX_LENGTH = 28; // 子网掩码
private static final int MAX_RETRY = 5;
private ParcelFileDescriptor vpnInterface; // TUN 接口描述符
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
// 检查 V2ray 是否已启动,避免重复进程
if (!isV2rayRunning()) {
V2rayUtil.startV2Ray(getApplicationContext());
} else {
Log.w("CustomVpnService", "V2Ray already running, skipping redundant start.");
}
// 启动 VPN 流量服务
startVpn();
} catch (Exception e) {
Log.e("CustomVpnService", "Error in onStartCommand: " + e.getMessage(), e);
stopSelf(); // 发生异常时停止服务
}
return START_STICKY;
}
private boolean isV2rayRunning() {
try {
// 执行系统命令,获取当前所有正在运行的进程
Process process = Runtime.getRuntime().exec("ps");
// 读取进程的输出
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
// 检查是否有包含 "v2ray" 的进程
if (line.contains("v2ray")) {
Log.i("CustomVpnService", "V2Ray process found: " + line);
return true;
}
}
}
// 检查完成,没有找到 "v2ray" 相关的进程
Log.i("CustomVpnService", "No V2Ray process is running.");
return false;
} catch (IOException e) {
// 捕获异常并记录日志
Log.e("CustomVpnService", "Error checking V2Ray process: " + e.getMessage(), e);
return false;
}
}
private void startVpn() {
try {
// 配置虚拟网卡
Builder builder = new Builder();
// 不再需要手动验证 TUN_ADDRESS 和 PREFIX_LENGTH
// 直接使用系统权限建立虚拟网卡,用于 TUN 接口和流量捕获
builder.addAddress(TUN_ADDRESS, PREFIX_LENGTH); // 保证 TUN 接口 IP 地址仍与 v2ray 配置文件保持一致
builder.addRoute("0.0.0.0", 0); // 捕获所有 IPv4 流量
// DNS 部分,如果有需要,也可以简化或直接保留 v2ray 配置提供的
List<String> dnsServers = getSystemDnsServers();
if (dnsServers.isEmpty()) {
// 如果未能从系统中获取到 DNS 地址,添加备用默认值
builder.addDnsServer("8.8.8.8"); // Google DNS
builder.addDnsServer("8.8.4.4");
} else {
for (String dnsServer : dnsServers) {
if (isValidIpAddress(dnsServer)) {
builder.addDnsServer(dnsServer);
}
}
}
builder.setBlocking(true);
// 直接建立 TUN 虚拟接口
vpnInterface = builder.establish();
if (vpnInterface == null) {
Log.e(
"CustomVpnService",
"builder.establish() returned null. Check VpnService.Builder configuration and system state."
);
throw new IllegalStateException("VPN Interface establishment failed");
}
// 核心:启动流量转发服务,此后转发逻辑由 v2ray 接管
new Thread(() -> handleVpnTraffic(vpnInterface)).start();
} catch (Exception e) {
// 增强日志描述信息,方便调试
Log.e(
"CustomVpnService",
"startVpn failed: " + e.getMessage(),
e
);
}
}
// 工具方法:判断 IP 地址是否合法
private boolean isValidIpAddress(String ip) {
try {
InetAddress.getByName(ip);
return true;
} catch (UnknownHostException e) {
return false;
}
}
private List<String> getSystemDnsServers() {
List<String> dnsServers = new ArrayList<>();
try {
String command = "getprop | grep dns";
Process process = Runtime.getRuntime().exec(command);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("dns") && line.contains(":")) {
String dns = line.split(":")[1].trim();
if (isValidIpAddress(dns)) { // 添加有效性检查
dnsServers.add(dns);
}
}
}
}
} catch (IOException e) {
// 捕获问题日志
Log.e("CustomVpnService", "Error while fetching DNS servers: " + e.getMessage(), e);
}
// 添加默认 DNS 在无效情况下
if (dnsServers.isEmpty()) {
dnsServers.add("8.8.8.8");
dnsServers.add("8.8.4.4");
dnsServers.add("1.1.1.1");
}
return dnsServers;
}
@Override
public void onDestroy() {
super.onDestroy();
if (vpnInterface != null) {
try {
vpnInterface.close();
vpnInterface = null;
Log.d("CustomVpnService", "VPN interface closed.");
} catch (IOException e) {
Log.e("CustomVpnService", "Error closing VPN interface", e);
}
}
}
private void handleVpnTraffic(ParcelFileDescriptor vpnInterface) {
if (vpnInterface == null || !vpnInterface.getFileDescriptor().valid()) {
throw new IllegalArgumentException("ParcelFileDescriptor is invalid!");
}
byte[] packetData = new byte[32767];
final int maxRetry = 5; // 定义最大重试次数
int retryCount = 0;
FileInputStream inStream = new FileInputStream(vpnInterface.getFileDescriptor());
FileOutputStream outStream = new FileOutputStream(vpnInterface.getFileDescriptor());
// 将流放在 try-with-resources 之外,避免循环中被关闭
// 循环处理 VPN 数据包
while (!Thread.currentThread().isInterrupted()) {
try {
int length = inStream.read(packetData);
if (length == -1) {
// 读取结束
break;
}
if (length > 0) {
boolean handled = processPacket(packetData, length);
if (!handled) {
outStream.write(packetData, 0, length);
}
}
retryCount = 0; // 成功一次后重置重试次数
} catch (IOException e) {
retryCount++;
Log.e("CustomVpnService", "Error reading packet. Retry attempt " + retryCount, e);
if (retryCount >= maxRetry) {
Log.e("CustomVpnService", "Max retry reached. Exiting loop.");
break;
}
// 可添加短暂延迟来避免频繁重试
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
Log.e("CustomVpnService", "Thread interrupted during sleep", ie);
}
}
}
// 最终关闭 ParcelFileDescriptor
try {
vpnInterface.close();
} catch (IOException e) {
Log.e("CustomVpnService", "Failed to close vpnInterface", e);
}
}
private boolean processPacket(byte[] packetData, int length) {
if (packetData == null || length <= 0 || length > packetData.length) {
Log.w("CustomVpnService", "Invalid packetData or length");
return false;
}
try {
boolean isTcpPacket = checkIfTcpPacket(packetData);
boolean isDnsPacket = checkIfDnsRequest(packetData);
Log.d("CustomVpnService", "Packet Info: TCP=" + isTcpPacket + ", DNS=" + isDnsPacket + ", Length=" + length);
if (isTcpPacket || isDnsPacket) {
Log.i("CustomVpnService", "Forwarding to V2Ray. Packet Length: " + length);
return true;
}
} catch (ArrayIndexOutOfBoundsException e) {
Log.e("CustomVpnService", "Malformed packet data: out of bounds", e);
} catch (IllegalArgumentException e) {
Log.e("CustomVpnService", "Invalid packet content", e);
} catch (Exception e) {
Log.e("CustomVpnService", "Unexpected error during packet processing. Packet Length: " + length, e);
}
return false;
}
private boolean checkIfTcpPacket(byte[] packetData) {
// IPv4 数据包最前面 1 字节的前 4 位是版本号
int version = (packetData[0] >> 4) & 0xF;
if (version != 4) {
return false; // 非 IPv4不处理
}
// IPv4 中 Protocol 字段位于位置 9值为 6 表示 TCP 协议
int protocol = packetData[9] & 0xFF; // 取无符号位
return protocol == 6; // 6 表示 TCP
}
private boolean checkIfDnsRequest(byte[] packetData) {
// IPv4 UDP 协议号为 17
int protocol = packetData[9] & 0xFF;
if (protocol != 17) {
return false; // 不是 UDP
}
// UDP 源端口在 IPv4 头部后的第 0-1 字节(总长度 IPv4 Header 20 字节 + UDP Offset
// IPv4 头长度在第一个字节后四位
int ipHeaderLength = (packetData[0] & 0x0F) * 4;
int sourcePort = ((packetData[ipHeaderLength] & 0xFF) << 8) | (packetData[ipHeaderLength + 1] & 0xFF);
int destPort = ((packetData[ipHeaderLength + 2] & 0xFF) << 8) | (packetData[ipHeaderLength + 3] & 0xFF);
// 检查是否是 UDP 的 DNS 端口 (53)
return sourcePort == 53 || destPort == 53;
}}