在Android的OpenVPN Service的FAQ上,關於TAP模式有三問三答,最後回答的建議是:Support TAP via emulation。這也是我自己前幾個月實現過的。要問為何Android自己不能提供對TAPmode的支持,似乎不關VPNService作者本人的事,其建議是:
If you really want to see tap-style tunnels supported in OpenVPN Connect, we would encourage you tocontact the Google Android team and ask that the VpnService API be extended to allow this. Without such changes to the VpnService API, it is not possible for non-root apps such as OpenVPN Connect to support tap-style tunnels.
這似乎是個圈,但是我們仔細想一下就會發現這個圈原來是這樣:
1.Android平台在一個公共的層下面擁有多個完全不同的底層實現,包括Linux內核;
2.開放Android的root權限意味著用戶可以觸摸到任意的底層;
3.所有的公共集合,包括API以及功能必須不能基於底層來構建,root權限不能對所有普通用戶開放;
4.必須有一個公共的層,用戶的需求基於這個公共的層來實現
5.這個層就是Android平台。
另外,我們需要理解的是tun.ko驅動本身,它很短,它很簡單。它能在你的Linux BOX上load成功的原因是因為你的Linux BOX實現了Ethernet,看看你的內核config文件,是不是有:
CONFIG_NET_ETHERNET=y
這一行呢?實際上,tun的TAP mode嚴重依賴這個內核編譯選項。然而對於Android,由於其底層的Linux內核完全根據具體的device定制,你不能保證它一定會支持Ethernet。閒來無事可以看看http://source.android.com/source/building-kernels.html這個站點。
雖然大多數的Android設備都支持root用戶使用TAP mode的tun驅動,但是這說明不了什麼問題,你依然無法保證Ethernet的支持是必須的。上述的自環解釋圈說明了,Android平台不應該基於root用戶可以觸摸的完全的組件來構建...
當然,在VPNService中你還是可以使用TAP mode的!答案在於將這個TAP mode也構建於Android平台之上,怎麼做?Support TAP via emulation!我已經做好了一個,辦法很簡單,就是在OpenVPN中內置了一個ARP處理以及Ethernet封裝/解封裝模塊,代碼來自uIP。這不是重點,重點是,難道非得這麼任性嗎?為何TUN mode就是不可以?!同樣來自Android的FAQ,以下的話可能會為TUN mode加上幾分:
The configuration of the VPN tunnel consists of the IP address and the networks that should be routed over this interface. Especially, no peer partner address or gateway address is needed or required. Special routes to reach the VPN Server (for example added when using redirect-gateway) are not needed either. The application will consequently ignore these settings when importing a configuration. The app ensures with the VPNService API that the connection to the server is not routed through the VPN tunnel.
是的,正如你看到的,使用TUN mode的話會省去很多配置,你不再需要對端的虛擬網卡IP地址信息,隧道建立之後,配置路由時,你只需要指定路由出口而不必指出下一跳,事實上,由於TUN mode的虛擬網卡處於點對點模式(下一跳是隱含且明確的!),而只有多路訪問模式的網卡才需要解析下一跳。
進入本日志的第二個話題。Android 4.4+使用了策略路由機制來添加通過OpenVPN虛擬網卡的路由。之所以寫這個是因為一段插曲...
大概一個多月前的某天,和同事一起去客戶那裡排查一個問題,過程中用adb登錄到了Android的後台,啟動VPN後,例行地運行了iproute2命令集(只要有這個工具,幾乎可以解決98.7512%的問題),然後就是iptables-save命令,驚奇地產生了挫敗感,整個人瞬間凌亂...太混亂了,竟然有打IPMARK的規則,ip ru ls竟然可以看到多出來的策略路由表,mark好像是0x3c什麼的...我瞬間崩潰的原因是因為我意識到我還債的時候到了,在不由自主地罵了幾句後,客戶那邊的人也跟著附和...從來沒人動過這個系統,iproute2+iptables+mark的風格以及策略路由表的命名風格又和我的風格是如此類似,我承認了”這是我幾個月前來的時候加的調試手段...“,其實我自己也不知道怎麼回事...問題是我身邊的人好像只有我會干出這種事,命名規則看起來又不像是廠商的手筆,那就一定是我干的!這是一個理所當然的推理。...那天還好算是把問題解決了。之後我就一直是回憶回憶回憶,什麼時候干過那事呢?百思不得其解,直到碰到了又一個問題,我的另一個同事說Android 4.4的路由沒法添加。當然,沒法加路由意味著OpenVPN不能使用,這是一個很嚴重的問題。後來就是google+iproute2了,後來還真的發現了ip rule中有端倪,確實添加了新的路由表專門處理和OpenVPN的數據通道相關的數據包!於是google確認,發現了下面的文字:
Routing/Interface Configuration
The Routing and interface configuration is not done via traditional ifconfig/route commands but by using the VPNService API. This results in a different routing configuration than on other OSes.
The configuration of the VPN tunnel consists of the IP address and the networks that should be routed over this interface. Especially, no peer partner address or gateway address is needed or required. Special routes to reach the VPN Server (for example added when using redirect-gateway) are not needed either. The application will consequently ignore these settings when importing a configuration. The app ensures with the VPNService API that the connection to the server is not routed through the VPN tunnel.
The VPNService API does not allow specifying networks that should not be routed via the VPN. As a workaround the app tries to detect networks that should not be routed over tunnel (e.g. route x.x.x.x y.y.y.y net_gateway) and calculates a set of routes that excludes this routes to emulate the behaviour of other platforms. The log windows shows the configuration of the VPNService upon establishing a connection.
Behind the scenes: Android 4.4+ does use policy routing. Using route/ifconfig will not show the installed routes. Instead use ip rule, iptables -t mangle -L
請注意最後一段!今後千萬別胡亂承認一些不是自己干的事情了,持同樣想法的人太多了,人本來就是以群分的。另外,要對自己有信心,廠商的開發者也和自己一樣,會犯錯誤,會不規范,當然解決問題的風格也可能和自己是一模一樣的,畢竟知識是共享的,而我可能恰好又和他共享了一點相同的文化,所不同的可能只是,他在谷歌(只是可能),而我沒有(能力進去)。