2

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.

axel8888
  • 61
  • 2
  • 7
  • Is your fd valid? Can you post code? Might be help with troubleshooting. – dudebrobro Feb 14 '14 at 03:36
  • @Quentin Swain Just modify the ToyVPN sample. handshake(tunnel) finish the start-control-request and outgoing-call-request tcp communication ,and then server start sending PPP LCP packets repeatly to me, but I can't receive any words from anywhere. – axel8888 Feb 14 '14 at 03:49

1 Answers1

0

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.

dudebrobro
  • 1,287
  • 10
  • 17
  • truly I modifyed handshake because the original one couldn't work.Isn't it just send the string from the EditText "share secret" to tunnel? The VPN server will actually send back message only while I post a start-control-request packet.Otherwise, nothing happens. – axel8888 Feb 14 '14 at 06:33
  • Calling establish() twice on the builder may be the problem. Now I can read and write fd normally, but what is the data read from fd? In System VPN info I see the "sending byte/Packages" has value while "recv byte/Packages" has no value. I only get many bytes from fd which meaningless to me. – axel8888 Feb 14 '14 at 08:58
  • Their original example has some server code included in the build tree and yes that is all it did to keep the example simple. The fd returned from establish is associated with a tun device that represets the vpn interfave devuce. When you read from the descriptor you are reading the network data being transmitted across the vpn connection and writes to the fd send data out. I would expect that implementing a vpn you have to do some processing of the raw data that you are getting over the vpn connection – dudebrobro Feb 14 '14 at 10:08
  • Thanks. But I still want to know how to transport PPP LCP configure packets to server? Obviously, socket will have no help. I tried to read from /dev/ppp but permission denied. Even for a rooted device. I can read many data packets from the fd now, but in the net data stream from tcpdump, I cound not find any packets which can match the data from the fd. – axel8888 Feb 15 '14 at 02:41
  • Have you tried writing data out if the fd to see if it goes out to your pptp server. Implementing a vpn service lets you implement whatever protocol you are trying to support to make your vpn app work. – dudebrobro Feb 16 '14 at 17:13
  • Yes, but tcpdump cannot catch any avaliable packets. Now the question is just how to transport PPP LCP packets through the fd and the tunnel. After VPN service established, it seems all data write to tunnel lost. – axel8888 Feb 17 '14 at 11:27