
利用 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 注释能更详细就好了 |