web-dev-qa-db-fra.com

Pare-feu Android avec VpnService

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.

Google Play App Store

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:

capture d'écran

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());

    }

}
28
fatihdurmus

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.

16
Paul Lammertsma

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.

0
Ali