简体   繁体   中英

Android development, FileDescriptor from VPNService.buider cannot write and read

I'm trying to use the Android4.x VPN Service to establish a VPN tunnel with inner Ethernet server.the IP address is a globle ip on Internet.Now here is the problems:

1.I use TCP dump to catch packets, after a VPN Service.build established, none of tcp packets can be transport in the tunnel, which was connected to server before.

2.after the build established, I get a fileDescriptor, it cannot write any bytes(EINVAL error), and cannot read any bytes(length = 0).

3.I use the socket tunnel to communicate to the server and send PPTP packet, after start-control-request, outgoing-call-request, the server returned correct information and then transport configure information through PPP LCP protocol. However, I don't know what to do next, how to get the PPP LCP packet?It's not from socket, and file Descriptor can't read or write anything.

Please help, thanks everyone!

private static ParcelFileDescriptor tunPFD;

private boolean run(InetSocketAddress server) throws Exception {
    SocketChannel tunnel = null;
    boolean connected = false;
    try {

        // Create a DatagramChannel as the VPN tunnel.
        tunnel = SocketChannel.open();

        // Protect the tunnel before connecting to avoid loopback.
        if (!protect(tunnel.socket())) {

            // throw new IllegalStateException("Cannot protect the tunnel");
            System.out.println("can't protected");
        }

        // Connect to the server.
        tunnel.connect(server);
        System.out.println("connected");

        // For simplicity, we use the same thread for both reading and
        // writing. Here we put the tunnel into non-blocking mode.     
        tunnel.configureBlocking(true);

        System.out.println("PFD success");

        // Authenticate and configure the virtual network interface.
        handshake(tunnel);
        System.out.println("handshake");
        Thread.sleep(1000);
        ToyVpnService.Builder builder = new ToyVpnService.Builder();
        builder.setSession("ToyVPN").addAddress("xxx.xxx.xxx.xxx", 32)
                                    .addRoute("1.0.0.0", 8)
                                    .addRoute("2.0.0.0", 7)
                                    .addRoute("4.0.0.0", 6)
                                    .addRoute("8.0.0.0", 7)
                                    .addRoute("11.0.0.0", 8)
                                    .addRoute("12.0.0.0", 6)
                                    .addRoute("16.0.0.0", 4)
                                    .addRoute("32.0.0.0", 3)
                                    .addRoute("64.0.0.0", 2)
                                    .addRoute("139.0.0.0", 8)
                                    .addRoute("140.0.0.0", 6)
                                    .addRoute("144.0.0.0", 4)
                                    .addRoute("160.0.0.0", 5)
                                    .addRoute("168.0.0.0", 6)
                                    .addRoute("172.0.0.0", 12)
                                    .addRoute("172.32.0.0", 11)
                                    .addRoute("172.64.0.0", 10)
                                    .addRoute("172.128.0.0", 9)
                                    .addRoute("173.0.0.0", 8)
                                    .addRoute("174.0.0.0", 7)
                                    .addRoute("176.0.0.0", 4)
                                    .addRoute("192.0.0.0", 9)
                                    .addRoute("192.128.0.0", 11)
                                    .addRoute("192.160.0.0", 13)
                                    .addRoute("192.169.0.0", 16)
                                    .addRoute("192.170.0.0", 15)
                                    .addRoute("192.172.0.0", 14)
                                    .addRoute("192.176.0.0", 12)
                                    .addRoute("192.192.0.0", 10)
                                    .addRoute("193.0.0.0", 8)
                                    .addRoute("194.0.0.0", 7)
                                    .addRoute("196.0.0.0", 6)
                                    .addRoute("200.0.0.0", 5)
                                    .addRoute("208.0.0.0", 4)
                                    .addRoute("224.0.0.0", 4)
                                    .addRoute("240.0.0.0", 5)
                                    .addRoute("248.0.0.0", 6)
                                    .addRoute("252.0.0.0", 7)
                                    .addRoute("254.0.0.0",8)
                                    .addDnsServer("xxx.xxx.xxx.xxx")
                                    .establish();

        if (tunPFD == null) {
            tunPFD = builder.establish();
            if (tunPFD == null) {
               System.out.println("stop");
               stopSelf();
            }
        }

        // Now we are connected. Set the flag and show the message.
        connected = true;
        mHandler.sendEmptyMessage(R.string.connected);
        tunnel.configureBlocking(false);

        // Packets to be sent are queued in this input stream.
       FileInputStream in = new FileInputStream(tunPFD.getFileDescriptor());

       // Packets received need to be written to this output stream.
       FileOutputStream out = new FileOutputStream(tunPFD.getFileDescriptor());
       int length = 0;
       int count = 0;

       while ((length == 0) && (count < 5000)) {
           length = in.read(pptp.dataPack);
           Thread.sleep(200);
           count += 200;
           System.out.println(count);
       }

       System.out.printf("read fd%d\n", tunPFD.getFd());
       System.out.println(length);

       System.out.println("write fd");
       tunnel.write(pptp.packet);

       Thread.sleep(2000);


        } catch (InterruptedException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                tunnel.close();
        } catch (Exception e) {             
            // ignore
        }
    }
    return connected;
}
    private void handshake(SocketChannel tunnel) throws Exception {
    // To build a secured tunnel, we should perform mutual authentication
    // and exchange session keys for encryption. To keep things simple in
    // this demo, we just send the shared secret in plaintext and wait
    // for the server to send the parameters.

    // Allocate the buffer for handshaking.

    // Control messages always start with zero.

    tunnel.write(pptp.Start_Control_Req_Package());

    // Wait for the parameters within a limited time.
    Thread.sleep(100);
    // Normally we should not receive random packets.
    int length = tunnel.read(pptp.getEmptyPackage());
    if (length <= 0 || pptp.getPacketType() != 2) {
        System.out.println("start reply fail");
        return;
    }

    tunnel.write(pptp.Outgoing_Call_Req_Package());
    Thread.sleep(100);
    length = tunnel.read(pptp.getEmptyPackage());
    if (length <= 0 || pptp.getPacketType() != 8) {
        System.out.println("outgoing reply fail");
        return;
    }
    System.out.println("succeed");
}

pptp.Start_Control_Req_Package() guarantee to make a Start-Control-Request packet which can be reply by server. I have confirmed from tcpdump. Outgoing_Call is just the same. Then the server send back a PPP_LCP packet to request configuration, I don't know how to catch it and send back configurations.

Looking at your snippet of code i see a couple of things. Did you modify the handshake call? If not you looks like you could potentially be calling establish() twice on the builder. When you call establish in your code snippet assuming that the handshake and configure methods from the toyvpn example weren't modified you could be blowing away the the interface that is correctly configured to talk to the server at least from what i see looking at the vanilla toyvpn app their server is configured to send them the correct configuration. so you are trying to read and write from an incorrectly configured tun device.

private void handshake(DatagramChannel tunnel) throws Exception {
    // To build a secured tunnel, we should perform mutual authentication
    // and exchange session keys for encryption. To keep things simple in
    // this demo, we just send the shared secret in plaintext and wait
    // for the server to send the parameters.
    // Allocate the buffer for handshaking.
    ByteBuffer packet = ByteBuffer.allocate(1024);

    // Control messages always start with zero.
    packet.put((byte) 0).put(mSharedSecret).flip();

    // Send the secret several times in case of packet loss.
    for (int i = 0; i < 3; ++i) {
        packet.position(0);
        tunnel.write(packet);
    }
    packet.clear();

    // Wait for the parameters within a limited time.
    for (int i = 0; i < 50; ++i) {
        Thread.sleep(100);

        // Normally we should not receive random packets.
        int length = tunnel.read(packet);
        if (length > 0 && packet.get(0) == 0) {
            configure(new String(packet.array(), 1, length - 1).trim());
            return;
        }
    }
    throw new IllegalStateException("Timed out");
}

private void configure(String parameters) throws Exception {
    // If the old interface has exactly the same parameters, use it!
    if (mInterface != null && parameters.equals(mParameters)) {
        Log.i(TAG, "Using the previous interface");
        return;
    }

    // Configure a builder while parsing the parameters.
    Builder builder = new Builder();
    for (String parameter : parameters.split(" ")) {
        String[] fields = parameter.split(",");
        try {
            switch (fields[0].charAt(0)) {
                case 'm':
                    builder.setMtu(Short.parseShort(fields[1]));
                    break;
                case 'a':
                    builder.addAddress(fields[1], Integer.parseInt(fields[2]));
                    break;
                case 'r':
                    builder.addRoute(fields[1], Integer.parseInt(fields[2]));
                    break;
                case 'd':
                    builder.addDnsServer(fields[1]);
                    break;
                case 's':
                    builder.addSearchDomain(fields[1]);
                    break;
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("Bad parameter: " + parameter);
        }
    }

    // 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(mServerAddress)
            .setConfigureIntent(mConfigureIntent)
            .establish();
        mParameters = parameters;
        Log.i(TAG, "New interface: " + parameters);
    }
}

Potentially three times because the second time the return value from the builder isn't used but it would return a new fd for the tun device according to the docs. I would probably suggest moving the changes that you are adding for configuring the vpn interface into the configure method in the ToyVpnService example. For the most part it looks like most of your changes are focused on the configuration. You could try adding calls to canCheckError / checkError from the ParcelFileDescriptor interface or use getFd() and call valid to check that the descriptor for the tun device is actually a valid fd by the time you try to read and write to it.

Hope that helps some.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM