4

我正在尝试以编程方式建立并连接到我们自己的 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

在使用数据报通道时,我得到一个 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)

我的VPN服务:

    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} 对。

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

ToyVPN 和 OpenVPN 不起作用。

4

1 回答 1

1

虽然有可能,但并不容易,因为:

  • " VpnService" 类只是一个Virtual Network Adapter/Interface可以被视为/使用的简单的Fire-WallNetGuard
  • 如果您想创建一个 VPN 应用程序,该类将确保您不需要root该设备。
  • 但是所有的连接和数据传输都必须从你这边编码,这是硬网络的东西。
  • 另一方面,即使很难喜欢,也有人这样做/正在这样做。
于 2018-04-24T05:52:31.127 回答