Android:使用VPNService的VPN连接

时间:2017-06-08 03:32:34

标签: android android-vpn-service

我正在尝试以编程方式建立并连接到我们自己的vpn(不是默认的VPN提供商,即PPTP,L2TP等,它存在于Android设置 - >无线和网络中)。

我想知道这是否已经可以在2017年实现。

我使用的参考

  1. http://www.thegeekstuff.com/2014/06/android-vpn-service/
  2. https://android.googlesource.com/platform/development/+/master/samples/ToyVpn/src/com/example/android/toyvpn
  3. 使用数据报通道时,我收到PortUnreachableException。这就是我的代码:

    @Override
    public void run() {
        try {
            Log.i(getTag(), "Starting");
            // If anything needs to be obtained using the network, get it now.
            // This greatly reduces the complexity of seamless handover, which
            // tries to recreate the tunnel without shutting down everything.
            // In this demo, all we need to know is the server address.
            final SocketAddress serverAddress = new InetSocketAddress(mServerName, mServerPort);
            // We try to create the tunnel several times.
            // TODO: The better way is to work with ConnectivityManager, trying only when the
            //       network is available.
            // Here we just use a counter to keep things simple.
            for (int attempt = 0; attempt < 10; ++attempt) {
                // Reset the counter if we were connected.
                if (run(serverAddress)) {
                    attempt = 0;
                }
                // Sleep for a while. This also checks if we got interrupted.
                Thread.sleep(3000);
            }
            Log.i(getTag(), "Giving up");
        } catch (IOException | InterruptedException | IllegalArgumentException e) {
            Log.e(getTag(), "Connection failed, exiting", e);
        }
    }
    private boolean run(SocketAddress server)
            throws IOException, InterruptedException, IllegalArgumentException {
        ParcelFileDescriptor iface = null;
        boolean connected = false;
        // Create a DatagramChannel as the VPN tunnel.
        try (DatagramChannel tunnel = DatagramChannel.open()) {
            // Protect the tunnel before connecting to avoid loopback.
            if (!mService.protect(tunnel.socket())) {
                throw new IllegalStateException("Cannot protect the tunnel");
            }
            // Connect to the server.
            tunnel.connect(server);
            // For simplicity, we use the same thread for both reading and
            // writing. Here we put the tunnel into non-blocking mode.
            tunnel.configureBlocking(false);
            // Authenticate and configure the virtual network interface.
            iface = handshake(tunnel);
            // Now we are connected. Set the flag.
            connected = true;
            // Packets to be sent are queued in this input stream.
            FileInputStream in = new FileInputStream(iface.getFileDescriptor());
            // Packets received need to be written to this output stream.
            FileOutputStream out = new FileOutputStream(iface.getFileDescriptor());
            // Allocate the buffer for a single packet.
            ByteBuffer packet = ByteBuffer.allocate(MAX_PACKET_SIZE);
            // Timeouts:
            //   - when data has not been sent in a while, send empty keepalive messages.
            //   - when data has not been received in a while, assume the connection is broken.
            long lastSendTime = System.currentTimeMillis();
            long lastReceiveTime = System.currentTimeMillis();
            // We keep forwarding packets till something goes wrong.
            while (true) {
                // Assume that we did not make any progress in this iteration.
                boolean idle = true;
                // Read the outgoing packet from the input stream.
                int length = in.read(packet.array());
                if (length > 0) {
                    // Write the outgoing packet to the tunnel.
                    packet.limit(length);
                    tunnel.write(packet);
                    packet.clear();
                    // There might be more outgoing packets.
                    idle = false;
                    lastReceiveTime = System.currentTimeMillis();
                }
                // Read the incoming packet from the tunnel.
                length = tunnel.read(packet);
                if (length > 0) {
                    // Ignore control messages, which start with zero.
                    if (packet.get(0) != 0) {
                        // Write the incoming packet to the output stream.
                        out.write(packet.array(), 0, length);
                    }
                    packet.clear();
                    // There might be more incoming packets.
                    idle = false;
                    lastSendTime = System.currentTimeMillis();
                }
                // If we are idle or waiting for the network, sleep for a
                // fraction of time to avoid busy looping.
                if (idle) {
                    Thread.sleep(IDLE_INTERVAL_MS);
                    final long timeNow = System.currentTimeMillis();
                    if (lastSendTime + KEEPALIVE_INTERVAL_MS <= timeNow) {
                        // We are receiving for a long time but not sending.
                        // Send empty control messages.
                        packet.put((byte) 0).limit(1);
                        for (int i = 0; i < 3; ++i) {
                            packet.position(0);
                            tunnel.write(packet);
                        }
                        packet.clear();
                        lastSendTime = timeNow;
                    } else if (lastReceiveTime + RECEIVE_TIMEOUT_MS <= timeNow) {
                        // We are sending for a long time but not receiving.
                        throw new IllegalStateException("Timed out");
                    }
                }
            }
        } catch (SocketException e) {
            Log.e(getTag(), "Cannot use socket", e);
        } finally {
            if (iface != null) {
                try {
                    iface.close();
                } catch (IOException e) {
                    Log.e(getTag(), "Unable to close interface", e);
                }
            }
        }
        return connected;
    }
    

    错误信息:

        E/ToyVpnConnection[1]: Cannot use socket java.net.PortUnreachableException at sun.nio.ch.DatagramDispatcher.read0(Native Method)
                                                                                         at sun.nio.ch.DatagramDispatcher.read(DatagramDispatcher.java:42)
                                                                                         at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
                                                                                         at sun.nio.ch.IOUtil.read(IOUtil.java:197)
                                                                                         at sun.nio.ch.DatagramChannelImpl.read(DatagramChannelImpl.java:566)
                                                                                         at org.droidplanner.android.fragments.control.ToyVpnConnection.handshake(ToyVpnConnection.java:219)
                                                                                         at org.droidplanner.android.fragments.control.ToyVpnConnection.run(ToyVpnConnection.java:120)
                                                                                         at org.droidplanner.android.fragments.control.ToyVpnConnection.run(ToyVpnConnection.java:93)
    

    MyVpnService:

        class MyVpnService  extends VpnService{
    private static final String TAG = MyVpnService.class.getSimpleName();
    
    private Thread mThread;
    private ParcelFileDescriptor mInterface;
    //a. Configure a builder for the interface.
    Builder builder = new Builder();
    public static final String ACTION_CONNECT = "com.example.android.toyvpn.START";
    
    public static final String ACTION_DISCONNECT = "com.example.android.toyvpn.STOP";
    private Handler mHandler;
    private PendingIntent mConfigureIntent;
    private final AtomicReference<Thread> mConnectingThread = new AtomicReference<>();
    private final AtomicReference<Connection> mConnection = new AtomicReference<>();
    private AtomicInteger mNextConnectionId = new AtomicInteger(1);
    private static class Connection extends Pair<Thread, ParcelFileDescriptor> {
        public Connection(Thread thread, ParcelFileDescriptor pfd) {
            super(thread, pfd);
        }
    }
    
    @Override
    public void onCreate() {
        Log.e("MyVpnService","onCreate");
    
        // The handler is only used to show messages.
        if (mHandler == null) {
            mHandler = new Handler();
        }
    
        //Create the intent to "configure" the connection (just start ToyVpnClient).
        mConfigureIntent = PendingIntent.getActivity(this, 0, new Intent(this, ToyVpnClient.class),
                PendingIntent.FLAG_UPDATE_CURRENT);
    }
    
    // Services interface
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Start a new session by creating a new thread.
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //a. Configure the TUN and get the interface.
                    mInterface = builder.setSession("MyVPNService")
                            .addAddress("192.168.0.1", 24)
                            .addDnsServer("8.8.8.8")
                            .addRoute("0.0.0.0", 0).establish();
    
    
                    //b. Packets to be sent are queued in this input stream.
                    FileInputStream in = new FileInputStream(
                            mInterface.getFileDescriptor());
                    //b. Packets received need to be written to this output stream.
                    FileOutputStream out = new FileOutputStream(
                            mInterface.getFileDescriptor());
                    // Allocate the buffer for a single packet.
                    ByteBuffer packet = ByteBuffer.allocate(32767);
    
                    //c. The UDP channel can be used to pass/get ip package to/from server
                    DatagramChannel tunnel = DatagramChannel.open();
                    // Connect to the server, localhost is used for demonstration only.
                    tunnel.connect(new InetSocketAddress("61.31.92.159", 1723));
                    //tunnel.connect(new InetSocketAddress("127.0.0.1", 8087));
                    //d. Protect this socket, so package send by it will not be feedback to the vpn service.
                    protect(tunnel.socket());
                    //e. Use a loop to pass packets.
                    while (true) {
                        //get packet with in
                        //put packet to tunnel
                        //get packet form tunnel
                        //return packet with out
                        //sleep is a must
                        Log.e("MyVpnService","true");
    
                        Thread.sleep(100);
                    }
    
                } catch (Exception e) {
                    // Catch any exception
                    Log.e(TAG,"Exception:"+e.toString());
    
                    e.printStackTrace();
                } finally {
                    try {
                        if (mInterface != null) {
                            mInterface.close();
                            mInterface = null;
                        }
                    } catch (Exception e) {
                        Log.e(TAG,"Exception2:"+e.toString());
                    }
                }
            }
    
        }, "MyVpnRunnable");
    
        //start the service
        mThread.start();
    
        if (intent != null && ACTION_DISCONNECT.equals(intent.getAction())) {
            disconnect();
            return START_NOT_STICKY;
        } else {
            connect();
            return START_STICKY;
        }
    }
    
    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        if (mThread != null) {
            mThread.interrupt();
        }
        super.onDestroy();
    }
    
    private void connect() {
        // Become a foreground service. Background services can be VPN services too, but they can
        // be killed by background check before getting a chance to receive onRevoke().
        updateForegroundNotification(R.string.connecting);
        mHandler.sendEmptyMessage(R.string.connecting);
        // Extract information from the shared preferences.
        final SharedPreferences prefs = getSharedPreferences(ToyVpnClient.Prefs.NAME, MODE_PRIVATE);
        final String server = "61.31.92.159";//prefs.getString(ToyVpnClient.Prefs.SERVER_ADDRESS, "");
        final byte[] secret = "123456789".getBytes();//= prefs.getString(ToyVpnClient.Prefs.SHARED_SECRET, "").getBytes();
        final int port;
        try {
            port = Integer.parseInt("1723");//Integer.parseInt(prefs.getString(ToyVpnClient.Prefs.SERVER_PORT, ""));
        } catch (NumberFormatException e) {
            Log.e("MyVPN", "Bad port: " + prefs.getString(ToyVpnClient.Prefs.SERVER_PORT, null), e);
            return;
        }
        // Kick off a connection.
        startConnection(new ToyVpnConnection(
                this, mNextConnectionId.getAndIncrement(), server, port, secret));
    }
    private void disconnect() {
        mHandler.sendEmptyMessage(R.string.disconnected);
        setConnectingThread(null);
        setConnection(null);
        stopForeground(true);
    }
    private void updateForegroundNotification(final int message) {
        startForeground(1, new Notification.Builder(this)
                //.setSmallIcon(R.drawable.ic_vpn)
                .setContentText(getString(message))
                .setContentIntent(mConfigureIntent)
                .build());
    }
    private void startConnection(final ToyVpnConnection connection) {
        // Replace any existing connecting thread with the  new one.
        final Thread thread = new Thread(connection, "ToyVpnThread");
        setConnectingThread(thread);
        // Handler to mark as connected once onEstablish is called.
        connection.setConfigureIntent(mConfigureIntent);
        connection.setOnEstablishListener(new ToyVpnConnection.OnEstablishListener() {
            public void onEstablish(ParcelFileDescriptor tunInterface) {
                mHandler.sendEmptyMessage(R.string.connected);
                mConnectingThread.compareAndSet(thread, null);
                setConnection(new Connection(thread, tunInterface));
            }
        });
        thread.start();
    }
    
    private void setConnectingThread(final Thread thread) {
        final Thread oldThread = mConnectingThread.getAndSet(thread);
        if (oldThread != null) {
            oldThread.interrupt();
        }
    }
    private void setConnection(final Connection connection) {
        final Connection oldConnection = mConnection.getAndSet(connection);
        if (oldConnection != null) {
            try {
                oldConnection.first.interrupt();
                oldConnection.second.close();
            } catch (IOException e) {
                Log.e(TAG, "Closing VPN interface", e);
            }
        }
    }
    

    }

    更新

    一个。 ToyVPN不使用PPTP协议(它使用自己的)

    湾ToyVPN只是一个概念验证演示,它不支持多个{username,password}对。

    ℃。 API是OpenVPN的一个:http://code.google.com/p/ics-openvpn/ 这提供了一个您完全可以控制的潜在VPN解决方案(服务器也是开源的),但它不是PPTP或IPSec。如果您了解PPTP协议,则应该可以将其用作实现此类VPN客户端的模型。

    ToyVPN和OpenVPN无法正常工作。

1 个答案:

答案 0 :(得分:1)

虽然有可能,但自那以后就不容易了:

  • VpnService”类只是Virtual Network Adapter/Interface,可以像NetGuard那样被视为简单的Fire-Wall
  • 如果您想创建一个VPN应用,该类只会确保您不需要root设备
  • 但是,所有连接和数据传输必须从你身边编码,这是硬网络的东西
  • 另一方面,即使很难喜欢,也有人doing that