作者:琪米 時間: 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來實現。(如有轉發請註明出處哦!!!)