VPNServiceを使ったソースコードを公開します。起動するとネットに繋がらなくなるけど、端末から飛んでいくパケットが途中まで(現在最初のパケットのみ)見れます。VPN関係のライブラリの使い方が正しいか不明。Android 4.0からが対象。また、AndroidManifestに以下のパーミッションが必要。このプログラムを起動するとまず、ダイアログが表示されるのでOKを押します。あとは端末でブラウザ開くなりするとパケットを解析した結果がログに流れますので、それを見て興奮するためのものです。終了方法は通知にある鍵アイコンから切断を選ぶことで終了します。
<uses-permission android:name="android.permission.INTERNET"/>
あとAndroidManifest.xmlのapplication内に以下の記述が必要
<service android:name="net.d_kami.VPN" android:permission="android.permission.BIND_VPN_SERVICE"> <intent-filter> <action android:name="android.net.VpnService"/> </intent-filter> </service>
package net.d_kami; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.util.Enumeration; import android.net.VpnService; import android.os.ParcelFileDescriptor; import android.util.Log; import net.d_kami.packet.IPHeader; import net.d_kami.packet.UDPHeader; public class VPN extends VpnService { private ParcelFileDescriptor fd; @Override public void onCreate(){ try{ Builder builder = new Builder(); builder.setMtu(1500); builder.addAddress(getIPAddress(), 32); builder.addRoute("0.0.0.0", 0); builder.addDnsServer("8.8.8.8"); fd = ParcelFileDescriptor.fromFd(builder.establish().detachFd()); final FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fd); final FileOutputStream fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); final ByteBuffer packet = ByteBuffer.allocate(1600); Thread thread = new Thread(new Runnable(){ IPHeader ip = new IPHeader(); UDPHeader udp = new UDPHeader(); public void run(){ while(true){ try{ int n = fis.read(packet.array()); if(n > 0){ packet.position(0); Log.e("n", "" + packet.position()); ip.set(packet); Log.e("Src Address", ip.getSrcAddress()); Log.e("Dst Address", ip.getDstAddress()); Log.e("version", "" + ip.getVersion()); Log.e("PacketLength", "" + ip.getPacketLength()); Log.e("Protocol", Integer.toHexString(ip.getProtocol())); if(ip.getProtocol() == 17){ udp.set(packet); Log.e("SRC Port", "" + udp.getSrcPort()); Log.e("DST Port", "" + udp.getDstPort()); packet.position(0); DatagramChannel tunnel = DatagramChannel.open(); // Protect the tunnel before connecting to avoid loopback. if (!protect(tunnel.socket())) { throw new IllegalStateException("Cannot protect the tunnel"); } tunnel.connect(new InetSocketAddress(ip.getDstAddress(), udp.getDstPort())); tunnel.configureBlocking(false); do{ packet.position(0); packet.limit(n); Log.e("write", "" + tunnel.write(packet)); packet.clear(); packet.limit(1600); }while((n = fis.read(packet.array())) > 0); while((n = tunnel.read(packet)) == 0){ } tunnel.close(); Log.e("Read", "" + n); if(n > 0){ packet.position(0); printPacket(packet); fos.write(packet.array(), 0, n); } packet.clear(); packet.position(0); } } }catch(Exception e){ e.printStackTrace(); } } } }); thread.start(); }catch(Exception e){ e.printStackTrace(); } } void printIP(ByteBuffer packet){ IPHeader ip = new IPHeader(); ip.set(packet); Log.e("SRC Address", ip.getSrcAddress()); Log.e("DST Address", ip.getDstAddress()); Log.e("version", "" + ip.getVersion()); Log.e("HeaderLength", "" + ip.getHeaderLength() * 4); Log.e("Protocol", Integer.toHexString(ip.getProtocol())); packet.position(ip.getHeaderLength() * 4); } public void printPacket(ByteBuffer packet){ IPHeader ip = new IPHeader(); ip.set(packet); Log.e("TSRC Address", ip.getSrcAddress()); Log.e("TDST Address", ip.getDstAddress()); Log.e("Tversion", "" + ip.getVersion()); Log.e("THeaderLength", "" + ip.getHeaderLength() * 4); Log.e("TProtocol", Integer.toHexString(ip.getProtocol())); if(ip.getProtocol() == 17){ UDPHeader udp = new UDPHeader(); udp.set(packet); Log.e("TSRC Port", "" + udp.getSrcPort()); Log.e("TDST Port", "" + udp.getDstPort()); } packet.position(0); } private static String getIPAddress() throws IOException{ Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); while(interfaces.hasMoreElements()){ NetworkInterface network = interfaces.nextElement(); Enumeration<InetAddress> addresses = network.getInetAddresses(); while(addresses.hasMoreElements()){ String address = addresses.nextElement().getHostAddress(); //127.0.0.1と0.0.0.0以外のアドレスが見つかったらそれを返す if(!"127.0.0.1".equals(address) && !"0.0.0.0".equals(address) && !"::1%1".equals(address)){ Log.e("address", address); return address; } } } return "127.0.0.1"; } }
次にIPヘッダを表すクラス。IPヘッダの解析して、変数にセットしているだけです。
package net.d_kami.packet; import java.nio.ByteBuffer; /** * IPv4ヘッダを表すクラス。送信元IPアドレスや送信先IPアドレスを格納する * * @author d-kami */ public class IPHeader{ /** このIPヘッダのバージョン */ private int version; /** このヘッダの長さ */ private int headerLength; /** IPパケットの優先順位 */ private int serviceType; /** IPパケットの長さ */ private int packetLength; /** 識別番号 */ private int id; /** IPパケットの分割を行うかどうかのフラグ */ private int flag; /** 分割されたパケットが元のパケットのどの位置にあるか */ private int offset; /** 生存時間 */ private int ttl; /** 上位層のプロトコル番号 */ private int protocol; /** チェックサム */ private int checksum; /** 送信元IPアドレス */ private String srcAddress; /** 送信先IPアドレス */ private String dstAddress; public void set(ByteBuffer packet){ int first = packet.get(); version = (first >> 4) & 0x0F; headerLength = first & 0x0F; serviceType = packet.get() & 0xFF; packetLength = packet.getShort() & 0xFFFF; id = packet.getShort() & 0xFFFF; int flagPosition = packet.getShort(); flag = (flagPosition >> 13) & 0x0F; offset = flagPosition & 0x1FFF; ttl = packet.get() & 0xFF; protocol = packet.get() & 0xFF; checksum = packet.getShort() & 0xFFFF; srcAddress = ip2Str(packet); dstAddress = ip2Str(packet); } /** * このIPヘッダのバージョンを設定する * * @param version このIPヘッダのバージョン */ public void setVetsion(int version){ this.version = version; } /** * このIPヘッダのバージョンを返す * * @return このIPヘッダのバージョン */ public int getVersion(){ return version; } /** * このIPヘッダの長さを設定する * * @param headerLength このIPヘッダの長さ */ public void setHeaderLength(int headerLength){ this.headerLength = headerLength; } /** * このIPヘッダの長さを返す * * @return このIPヘッダの長さ */ public int getHeaderLength(){ return headerLength; } /** * このIPパケットの優先順位を設定する * * @param packet このIPパケットの優先順位 */ public void setServiceType(int serviceType){ this.serviceType = serviceType; } /** * このIPパケットの優先順位を返す * * @return このIPパケットの優先順位 */ public int getServiceType(){ return serviceType; } /** * このIPパケットの長さを設定する * * @param packet このIPパケットの長さ */ public void setPacketLength(int packetLength){ this.packetLength = packetLength; } /** * このIPパケットの長さを返す * * @return このIPパケットの長さ */ public int getPacketLength(){ return packetLength; } /** * このIPパケットの識別番号を設定する * * @param packet このIPパケットの識別番号 */ public void setId(int id){ this.id = id; } /** * このIPパケットの識別番号を返す * * @return このパケットの識別番号 */ public int getId(){ return id; } /** * このIPパケットが分割されてるかどうかのフラグを設定する * * @param packet このIPパケットが分割されてるかどうかのフラグ */ public void setFlag(int flag){ this.flag = flag; } /** * このIPパケットが分割されているかどうかのフラグを返す * * @return このIPパケットが分割されているかどうかのフラグ */ public int getFlag(){ return flag; } /** * このIPパケットの分割前のオフセットを設定する * * @param packet このIPパケットの分割前のオフセット */ public void setOffset(int offset){ this.offset = offset; } /** * このIPパケットの分割前のオフセットを返す * * @return このIPパケットの分割前のオフセット */ public int getOffset(){ return offset; } /** * このIPパケットの生存時間を設定する * * @param packet このIPパケットの生存時間 */ public void setTtl(int ttl){ this.ttl = ttl; } /** * このIPパケットの生存時間を返す * * @return このIPパケットの生存時間 */ public int getTtl(){ return ttl; } /** * このIPパケットの上位層のプロトコル番号を設定する * * @param packet このIPパケットの上位層のプロトコル番号 */ public void setProtocol(int protocol){ this.protocol = protocol; } /** * このIPパケットの上位層のプロトコル番号を返す * * @return このIPパケットの上位層のプロトコル番号 */ public int getProtocol(){ return protocol; } /** * このIPパケットのチェックサムを設定する * * @param packet このIPパケットのチェックサム */ public void setChecksum(int checksum){ this.checksum = checksum; } /** * このIPパケットのチェックサムを返す * * @return このIPパケットのチェックサム */ public int getChecksum(){ return checksum; } /** * このIPパケットの送信元アドレスを設定する * * @param packet このIPパケットの送信元アドレス */ public void setSrcAddress(String srcAddress){ this.srcAddress = srcAddress; } /** * このIPパケットの送信元アドレスを返す * * @return このIPパケットの送信元アドレス */ public String getSrcAddress(){ return srcAddress; } /** * このIPパケットの送信先アドレスを設定する * * @param packet このIPパケットの送信先アドレス */ public void setDstAddress(String srcAddress){ this.srcAddress = srcAddress; } /** * このIPパケットの送信先アドレスを返す * * @return このIPパケットの送信先アドレス */ public String getDstAddress(){ return dstAddress; } /** * このIPヘッダをbyte配列に変換する * * @return このIPヘッダをbyte配列に変換したもの */ public void toByteBuffer(ByteBuffer packet){ put(packet, (((version & 0xFF) << 4) | (headerLength & 0xFF)) & 0xFF); put(packet, serviceType); putShort(packet, headerLength); putShort(packet, id); put(packet, ((flag & 0x07) << 13) | (offset & 0x1FFF)); put(packet, ttl); put(packet, protocol); putShort(packet, checksum); str2IP(packet, srcAddress); str2IP(packet, dstAddress); } void put(ByteBuffer packet, int value){ packet.put((byte)value); } void putShort(ByteBuffer packet, int value){ packet.putShort((short)value); } private void str2IP(ByteBuffer packet, String ip){ String[] split = ip.split("\\."); for(int i = 0; i < 4; i++){ packet.put(Byte.parseByte(split[i])); } } private String ip2Str(ByteBuffer packet){ return String.format("%d.%d.%d.%d", packet.get() & 0xFF, packet.get() & 0xFF, packet.get() & 0xFF, packet.get() & 0xFF); } }
最後にUDPヘッダを表すクラス。IPヘッダと同じようなことをしているだけです。
package net.d_kami.packet; import java.nio.ByteBuffer; public class UDPHeader { private int srcPort; private int dstPort; private int length; private int checksum; public void set(ByteBuffer packet){ if(packet == null){ throw new IllegalStateException("渡されたパケットはUDPパケットではありません"); } setSrcPort(packet); setDstPort(packet); setLength(packet); setChecksum(packet); } private void setSrcPort(ByteBuffer packet){ srcPort = packet.getShort() & 0xFFFF; } public void setSrcPort(int srcPort){ this.srcPort = srcPort; } public int getSrcPort(){ return srcPort; } private void setDstPort(ByteBuffer packet){ dstPort = packet.getShort() & 0xFFFF; } public void setDstPort(int dstPort){ this.dstPort = dstPort; } public int getDstPort(){ return dstPort; } private void setLength(ByteBuffer packet){ length = packet.getShort() & 0xFFFF; } public void setLength(int length){ this.length = length; } public int getLength(){ return length; } private void setChecksum(ByteBuffer packet){ checksum = packet.getShort() & 0xFFFF; } public void setChecksum(int checksum){ this.checksum = checksum; } public int getChecksum(){ return checksum; } public byte[] toByteArray(){ byte[] ret = new byte[8]; ret[0] = (byte)((srcPort & 0xFF) >> 8); ret[1] = (byte)(srcPort & 0xFF); ret[2] = (byte)((dstPort & 0xFF) >> 8); ret[3] = (byte)(dstPort & 0xFF); ret[4] = (byte)((length & 0xFF) >> 8); ret[5] = (byte)(length & 0xFF); ret[6] = (byte)((checksum & 0xFF) >> 8); ret[7] = (byte)(checksum & 0xFF); return ret; } }
最後にActivity
package net.d_kami; import android.net.VpnService; import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.util.Log; import android.view.Menu; public class VPNActivity extends Activity { private static final int VPN_REQUEST = 0x01; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = VpnService.prepare(this); if(intent != null){ startActivityForResult(intent, VPNActivity.VPN_REQUEST); }else{ Intent service = new Intent(this, VPN.class); startService(service); } setContentView(R.layout.activity_vpn); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data){ if(requestCode == VPNActivity.VPN_REQUEST && resultCode == Activity.RESULT_OK){ Intent service = new Intent(this, VPN.class); startService(service); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_vpn, menu); return true; } }
ついでにAndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.d_kami" android:versionCode="1" android:versionName="1.0" > <uses-permission android:name="android.permission.INTERNET"/> <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="14" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="net.d_kami.VPNActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="net.d_kami.VPN" android:permission="android.permission.BIND_VPN_SERVICE"> <intent-filter> <action android:name="android.net.VpnService"/> </intent-filter> </service> </application> </manifest>