作者:琪米 时间: 2018.8.02
引言: 最近就要离职了,总觉得不把自己做过的技术分享出来是一种遗憾。而且我认为技术要分享才能促进世界的进步(不好意思装了逼了),我怀着这个伟大的理想就开始了这个系列的文章。废话讲完下面开始讲正事。
我相信同是程序员很多人应该都对Google搜索情有独钟,奈何国内的墙太过厉害。阻碍了很多人的梦想,此时会有一批有志之士发挥了智慧建造了通向外面世界的梯子。这个梯子是怎么实现的呢?继续往下看吧!!
Google爸爸在4.0之后的Android系统放出一个强大的一个服务,叫做VpnServer,它位于android SDK的android.net的包下面。通过这个服务我们可以实现在应用层通过配置一些参数来打开底层luinx内核的tun网卡,通过这个tun网卡能够实现对android手机上的ip层的网络数据进行读写操作。
继承VpnServer服务
要使用这个服务,不能够直接使用,要先对这个服务进行继承。
public class DemoVpnServer extends VpnService
复制代码
配置基本参数建立一个底层的tun虚拟网卡
private ParcelFileDescriptor openTun()
{
Builder builder = new Builder();
//设置一个网卡的名字
builder.setSession("VpnDemo");
//设置ip数据包长度大小
builder.setMtu(1500);
//设置虚拟网卡ip地址
builder.addAddress("10.0.0.11",32);
//设置路由地址 0.0.0.0/0表示所有网络数据路由到虚拟网卡上面去 ps:可以设置多个
builder.addRoute("0.0.0.0",0);
//设置这个虚拟网卡的dns地址 ps:可以设置多个
builder.addDnsServer("8.8.8.8");
//建立虚拟网卡返回一个读取网卡数据的文件描述符
return builder.establish();
}
复制代码
打开了一个网卡之后,程序就可以直接读取底层网卡的ip数据包了。 具体代码如下:
public class DemoVpnServer extends VpnService implements Runnable{
private Thread mVpnRunThread;
private AtomicBoolean mVpnThreadRun;
private ParcelFileDescriptor openTun()
{
Builder builder = new Builder();
//设置一个网卡的名字
builder.setSession("VpnDemo");
//设置ip数据包长度大小
builder.setMtu(1500);
//设置虚拟网卡ip地址
builder.addAddress("10.0.0.11",32);
//设置路由地址 0.0.0.0/0表示所有网络数据路由到虚拟网卡上面去 ps:可以设置多个
builder.addRoute("0.0.0.0",0);
//设置这个虚拟网卡的dns地址 ps:可以设置多个
builder.addDnsServer("8.8.8.8");
//建立虚拟网卡返回一个读取网卡数据的文件描述符
return builder.establish();
}
public DemoVpnServer()
{
mVpnRunThread = new Thread(this);
mVpnThreadRun = new AtomicBoolean();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mVpnRunThread.start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
mVpnThreadRun.set(false);
}
@Override
public void run() {
mVpnThreadRun.set(true);
ParcelFileDescriptor fd = openTun();
FileOutputStream fileOutputStream = new FileOutputStream(fd.getFileDescriptor());
FileInputStream fileInputStream = new FileInputStream(fd.getFileDescriptor());
byte[] buffer = new byte[1600];
while (mVpnThreadRun.get())
{
try {
int length = fileInputStream.read(buffer);
if(length <= 0)
Thread.sleep(50);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
//关闭底层虚拟网卡
fd.close();
fileInputStream.close();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
复制代码
由于在虚拟网卡哪里配置的路由是0.0.0.0/0所以所有通过手机物理网卡得网络数据包都会路由到程序建立得tun虚拟网卡中去,然后从builder.establish();返回得文件描述符中可以把网卡得数据读出来,读得操作在这里
虽然在配置网卡数据得时候设置得ip数据包大小为1500,事实上偶然还是会有一些数据包得长度会超出1500,但是超出得幅度不大,所以我给读取每个buffer的时候大小是1600。而且读出来的长度也不一定是1500,这点需要自己根据返回的长度进行处理,ps:int length = fileInputStream.read(buffer)。最后还要在AndroidManifest.xml加上:
<service android:name=".DemoVpnServer"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
</service>
复制代码
下面运行一下程序看下前后的ip情况: vpnServer还没跑起来的情况:
vpnServer跑起来后的情况: 从上下两图可以看出,VpnServer还没运行起来的时候手机上是只有两个网卡网卡设备一个是以太网网卡设备eth0,一个是本地广播循环lo(可以理解为lo代表127.0.0.1,即localhost,不深究),当VpnServer运行起来并且建立一个虚拟网卡的时候,就多了一个叫做tun0的设备而且ip地址就是我们设置的ip和掩码地址10.0.0.11,(可能会有人问,上面不是写了32吗?怎么会是255.255.255.255,其实32是掩码的另一种表达方式叫做前缀长度,mask长度,即子网掩码为1的位数)。 还有一点需要注意的是,当每次打开VPN服务的时候需要向系统进行请求权限,代码如下:@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = VpnService.prepare(this);
if(intent!=null)
startActivityForResult(intent,1);
else
startService(new Intent(this,DemoVpnServer.class));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 1 && resultCode == RESULT_OK)
startService(new Intent(this,DemoVpnServer.class));
}
复制代码
启动一个VpnServer大致的流程图如下:
至此就完成了接管手机上网络数据的要求了。但是接管了手机的网络我们能干些什么呢?其实使用场景还是有不少地方,例如比较熟悉的VPN翻墙,代理翻墙,网络捉包工具,网络防火墙,广告拦截器等都可以使用VpnServer来实现。(如有转发请注明出处哦!!!)