J'essaie d'implémenter un pare-feu simple pour Android avec le projet VpnService for BS. J'ai choisi VpnService car il fonctionnera sur des périphériques non rootés. Il enregistre les connexions et vous permet de filtrer les connexions. (Basé sur IP)
Il y a une application qui fait cela, donc c'est possible.
J'ai fait des recherches et trouvé que VpnService crée une interface Tun. Rien de plus. (Pas d'implémentation VPN juste un tunnel) Il vous permet de donner une adresse à cette interface et d'ajouter des itinéraires. Il retourne un descripteur de fichier. Vous pouvez lire les paquets sortants et écrire les paquets entrants.
J'ai créé une classe dérivée de VpnService et j'ai démarré le service. Je peux configurer tun0
avec la classe VpnService.Builder. Quand je regarde la connexion mobiwol's
avec adb Shell netcfg
, cela crée une interface tun0
avec une adresse 10.2.3.4/32. Il achemine tous les paquets vers ce réseau privé et les envoie à Internet. J'essaye la même chose. Création d'une interface avec l'adresse 10.0.0.2/32. Ajout d'un itinéraire avec la fonction addRoute. 0.0.0.0/0 afin que je puisse capturer tous les paquets de tout le réseau pour autant que je sache. (Je suis assez nouveau sur ce sujet et j'apprends toujours. J'ai trouvé des articles sur Internet, donc je ne suis pas vraiment sûr. Corrigez-moi si je me trompe.)
J'ai créé 2 threads dans le service. On lit à partir du descripteur de fichier et l’écrit dans 127.0.0.1 avec un socket protégé. (Je ne sais pas vraiment si je devrais lire/écrire dans 127.0.0.1. C'est peut-être le problème.)
J'ai analysé les paquets que j'ai lus à partir du descripteur de fichier. Par exemple:
01000101 byte:69 //ipv4 20byte header
00000000 byte:0 //TOS
00000000 byte:0 //Total Length
00111100 byte:60 //Total Length
11111100 byte:-4 //ID
11011011 byte:-37 //ID
01000000 byte:64 //fragment
00000000 byte:0 //"
01000000 byte:64 //TTL
00000110 byte:6 //Protocol 6 -> TCP
01011110 byte:94 //Header checksum
11001111 byte:-49 //Header checksum
00001010 byte:10 //10.0.0.2
00000000 byte:0
00000000 byte:0
00000010 byte:2
10101101 byte:-83 //173.194.39.78 //google
00111110 byte:-62
00100111 byte:39
******** byte:78
10110100 byte:-76 // IP option
01100101 byte:101
00000001 byte:1
10111011 byte:-69
//20byte IP haeder
01101101 byte:109
. . //40byte data (i couldnt parse TCP header,
I think its not needed when I route this in IP layer)
. .
. .
00000110 byte:6
Je n'ai trouvé aucun autre en-tête IP dans le reste des données. Je pense qu'il devrait y avoir une encapsulation entre le réseau 10.0.0.2 et le réseau local (192.168.2.1) et Internet. Je ne suis pas sûr.
Mon vrai problème est que je suis bloqué sur le fil des paquets entrants. Je ne peux rien lire. Pas de réponse. Comme vous pouvez le voir sur la capture d'écran, aucune donnée entrante:
J'essaie de lire depuis la même connexion que celle que j'utilise pour écrire dans 127.0.0.1 avec socket protégé.
Interface Android <-> Tun (tun0) <-> connexion Internet
Tous les paquets <-> 10.0.0.2 <-> 127.0.0.1? <-> 192.168.2.1 <-> Internet?
Je n'ai rien trouvé d'utile à propos de VpnService. (L'exemple de ToyVPN est simplement inutile). Je lis des documents sur Linux Tun/Tap mais sur le tunneling entre hôte et distant. Je veux l'hôte et à distance sur le même appareil. Pas comme tunnel.
Comment puis-je faire ceci?
Edit: Code demandé. C'est très tôt. Comme je l'ai déjà mentionné, il s'agit d'une classe dérivée de VpnService. 2 threads (lecture et écriture) créés dans le thread de service.
package com.git.firewall;
public class GITVpnService extends VpnService implements Handler.Callback, Runnable {
private static final String TAG = "GITVpnService";
private String mServerAddress = "127.0.0.1";
private int mServerPort = 55555;
private PendingIntent mConfigureIntent;
private Handler mHandler;
private Thread mThread;
private ParcelFileDescriptor mInterface;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The handler is only used to show messages.
if (mHandler == null) {
mHandler = new Handler(this);
}
// Stop the previous session by interrupting the thread.
if (mThread != null) {
mThread.interrupt();
}
// Start a new session by creating a new thread.
mThread = new Thread(this, "VpnThread");
mThread.start();
return START_STICKY;
}
@Override
public void onDestroy() {
if (mThread != null) {
mThread.interrupt();
}
}
@Override
public boolean handleMessage(Message message) {
if (message != null) {
Toast.makeText(this, (String)message.obj, Toast.LENGTH_SHORT).show();
}
return true;
}
@Override
public synchronized void run() {
try {
Log.i(TAG, "Starting");
InetSocketAddress server = new InetSocketAddress(
mServerAddress, mServerPort);
run(server);
} catch (Exception e) {
Log.e(TAG, "Got " + e.toString());
try {
mInterface.close();
} catch (Exception e2) {
// ignore
}
Message msgObj = mHandler.obtainMessage();
msgObj.obj = "Disconnected";
mHandler.sendMessage(msgObj);
} finally {
}
}
DatagramChannel mTunnel = null;
private boolean run(InetSocketAddress server) throws Exception {
boolean connected = false;
Android.os.Debug.waitForDebugger();
// Create a DatagramChannel as the VPN tunnel.
mTunnel = DatagramChannel.open();
// Protect the tunnel before connecting to avoid loopback.
if (!protect(mTunnel.socket())) {
throw new IllegalStateException("Cannot protect the tunnel");
}
// Connect to the server.
mTunnel.connect(server);
// For simplicity, we use the same thread for both reading and
// writing. Here we put the tunnel into non-blocking mode.
mTunnel.configureBlocking(false);
// Authenticate and configure the virtual network interface.
handshake();
// Now we are connected. Set the flag and show the message.
connected = true;
Message msgObj = mHandler.obtainMessage();
msgObj.obj = "Connected";
mHandler.sendMessage(msgObj);
new Thread ()
{
public void run ()
{
// Packets to be sent are queued in this input stream.
FileInputStream in = new FileInputStream(mInterface.getFileDescriptor());
// Allocate the buffer for a single packet.
ByteBuffer packet = ByteBuffer.allocate(32767);
int length;
try
{
while (true)
{
while ((length = in.read(packet.array())) > 0) {
// Write the outgoing packet to the tunnel.
packet.limit(length);
debugPacket(packet); // Packet size, Protocol, source, destination
mTunnel.write(packet);
packet.clear();
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}.start();
new Thread ()
{
public void run ()
{
DatagramChannel tunnel = mTunnel;
// Allocate the buffer for a single packet.
ByteBuffer packet = ByteBuffer.allocate(8096);
// Packets received need to be written to this output stream.
FileOutputStream out = new FileOutputStream(mInterface.getFileDescriptor());
while (true)
{
try
{
// Read the incoming packet from the tunnel.
int length;
while ((length = tunnel.read(packet)) > 0)
{
// Write the incoming packet to the output stream.
out.write(packet.array(), 0, length);
packet.clear();
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
}.start();
return connected;
}
private void handshake() throws Exception {
if (mInterface == null)
{
Builder builder = new Builder();
builder.setMtu(1500);
builder.addAddress("10.0.0.2",32);
builder.addRoute("0.0.0.0", 0);
//builder.addRoute("192.168.2.0",24);
//builder.addDnsServer("8.8.8.8");
// Close the old interface since the parameters have been changed.
try {
mInterface.close();
} catch (Exception e) {
// ignore
}
// Create a new interface using the builder and save the parameters.
mInterface = builder.setSession("GIT VPN")
.setConfigureIntent(mConfigureIntent)
.establish();
}
}
private void debugPacket(ByteBuffer packet)
{
/*
for(int i = 0; i < length; ++i)
{
byte buffer = packet.get();
Log.d(TAG, "byte:"+buffer);
}*/
int buffer = packet.get();
int version;
int headerlength;
version = buffer >> 4;
headerlength = buffer & 0x0F;
headerlength *= 4;
Log.d(TAG, "IP Version:"+version);
Log.d(TAG, "Header Length:"+headerlength);
String status = "";
status += "Header Length:"+headerlength;
buffer = packet.get(); //DSCP + EN
buffer = packet.getChar(); //Total Length
Log.d(TAG, "Total Length:"+buffer);
buffer = packet.getChar(); //Identification
buffer = packet.getChar(); //Flags + Fragment Offset
buffer = packet.get(); //Time to Live
buffer = packet.get(); //Protocol
Log.d(TAG, "Protocol:"+buffer);
status += " Protocol:"+buffer;
buffer = packet.getChar(); //Header checksum
String sourceIP = "";
buffer = packet.get(); //Source IP 1st Octet
sourceIP += buffer;
sourceIP += ".";
buffer = packet.get(); //Source IP 2nd Octet
sourceIP += buffer;
sourceIP += ".";
buffer = packet.get(); //Source IP 3rd Octet
sourceIP += buffer;
sourceIP += ".";
buffer = packet.get(); //Source IP 4th Octet
sourceIP += buffer;
Log.d(TAG, "Source IP:"+sourceIP);
status += " Source IP:"+sourceIP;
String destIP = "";
buffer = packet.get(); //Destination IP 1st Octet
destIP += buffer;
destIP += ".";
buffer = packet.get(); //Destination IP 2nd Octet
destIP += buffer;
destIP += ".";
buffer = packet.get(); //Destination IP 3rd Octet
destIP += buffer;
destIP += ".";
buffer = packet.get(); //Destination IP 4th Octet
destIP += buffer;
Log.d(TAG, "Destination IP:"+destIP);
status += " Destination IP:"+destIP;
/*
msgObj = mHandler.obtainMessage();
msgObj.obj = status;
mHandler.sendMessage(msgObj);
*/
//Log.d(TAG, "version:"+packet.getInt());
//Log.d(TAG, "version:"+packet.getInt());
//Log.d(TAG, "version:"+packet.getInt());
}
}
Une question similaire a été posée il y a quelques mois , et bien que les réponses ne soient pas très éclairantes, les commentaires de la réponse acceptée donnent un aperçu de ce qui peut ne pas aller bien.
Vous devez garder à l’esprit quelle couche dans le modèle OSI votre logique réside:
Les flux entrants et sortants de VpnService se trouvent dans la couche réseau; vous recevez (et devez à son tour transmettre) des paquets IP bruts, comme vous l'avez décrit dans votre question.
Dans votre exemple de flux d'octets, vous pouvez voir que le flux d'octets entrant est un datagramme IPv4, car les quatre premiers bits sont 0100
(4). Consultez cette spécification de structure de paquet pour plus de détails sur IPv4.
Lors du transfert des demandes, vous vous trouvez dans la couche d'application. vous devriez transmettre le contenu de la charge UDP ou TCP (c'est-à-dire uniquement leurs données, pas les en-têtes eux-mêmes) en utilisant respectivement un DatagramSocket ou un Socket.
Gardez à l'esprit que cela ignore la couche de transport car ces implémentations prennent en charge la construction de l'en-tête UDP (dans le cas de DatagramSocket) et de l'en-tête TCP et des options (dans le cas de Socket).
Votre application devra essentiellement pouvoir interpréter et construire des en-têtes et des options IPv4 et IPv6, ainsi que des données utiles IP, des en-têtes UDP et des en-têtes et des options TCP.
Peut-être est-il préférable de rechercher des projets open source tels que OpenVpn . Il fonctionne au niveau API 14+ (Ice Cream Sandichich) sans accès root.