利用 Android 提供的 vpnservice 机制,可以拦截所有 ip 流量,接下来可以做任何你想做的事情
本文利用 vpnservice 实现了一个流量代理,能够转发 udp、tcp 协议,目前只是简单的转发到目标服务器,不做任何加工
项目地址:https://github.com/mightofcode/android-vpnservice
1,在 AndroidManifest.xml 中注册权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
2,创建一个类继承 VpnService
public class LocalVPNService extends VpnService
3,启动 vpnservice 执行意图
private void startVpn() {
Intent vpnIntent = VpnService.prepare(this);
if (vpnIntent != null)
startActivityForResult(vpnIntent, VPN_REQUEST_CODE);
else
onActivityResult(VPN_REQUEST_CODE, RESULT_OK, null);
}
启动 vpnservice
private void setupVPN() {
try {
if (vpnInterface == null) {
Builder builder = new Builder();
builder.addAddress(VPN_ADDRESS, 32);
builder.addRoute(VPN_ROUTE, 0);
builder.addDnsServer(Config.dns);
if (Config.testLocal) {
builder.addAllowedApplication("com.mocyx.basic_client");
}
vpnInterface = builder.setSession(getString(R.string.app_name)).setConfigureIntent(pendingIntent).establish();
}
} catch (Exception e) {
Log.e(TAG, "error", e);
System.exit(0);
}
}
4, 读写 vpnservice 文件描述符实现流量转发 创建两个 FileChannel
FileChannel vpnInput = new FileInputStream(vpnFileDescriptor).getChannel();
FileChannel vpnOutput = new FileOutputStream(vpnFileDescriptor).getChannel();
5, 从 input 中读取 ip 包并解析 如果是 tcp、udp,进行转发 其他协议( ICMP、IGMP )直接丢弃,丢弃 ICMP、IGMP 会有点问题,不过大部分 app 还是能正常使用的
while (!Thread.interrupted()) {
bufferToNetwork = ByteBufferPool.acquire();
int readBytes = vpnInput.read(bufferToNetwork);
MainActivity.upByte.addAndGet(readBytes);
if (readBytes > 0) {
bufferToNetwork.flip();
Packet packet = new Packet(bufferToNetwork);
if (packet.isUDP()) {
if (Config.logRW) {
Log.i(TAG, "read udp" + readBytes);
}
deviceToNetworkUDPQueue.offer(packet);
} else if (packet.isTCP()) {
if (Config.logRW) {
Log.i(TAG, "read tcp " + readBytes);
}
deviceToNetworkTCPQueue.offer(packet);
} else {
Log.w(TAG, String.format("Unknown packet protocol type %d", packet.ip4Header.protocolNum));
}
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
tcp 包的处理比较麻烦,需要处理握手、挥手等协议细节,需要正确处理 seq 号,等等,实现一个正确的 tcp 协议确实不容易
udp 只需要进行简单的转发
6,从输出队列中获取数据写入 output
ByteBuffer bufferFromNetwork = networkToDeviceQueue.take();
bufferFromNetwork.flip();
while (bufferFromNetwork.hasRemaining()) {
int w = vpnOutput.write(bufferFromNetwork);
if (w > 0) {
MainActivity.downByte.addAndGet(w);
}
if (Config.logRW) {
Log.d(TAG, "vpn write " + w);
}
}
7,app 使用:
点击 start 打开 vpn
点击 dns 进行一次 dns 请求
点击 http 进行一次 http 请求
最下方的文本框展示 io 字节数
1
xFrank 2020-02-14 16:21:19 +08:00
多谢分享
|
2
psuwgipgf 2020-02-14 17:28:56 +08:00
很好,
|
3
turi 2020-02-14 18:06:40 +08:00
国内这方面的真的少,
我去年写的时候,找了好多都没找到可用的,可能我把 fd 传到 native 中,让 native 层去收数据有关 |
5
redcoffeecat 2020-02-26 22:24:29 +08:00
注释能更详细就好了
|