Android:使用VPN服务的VPN连接
我正在尝试以编程方式建立并连接到我们自己的vpn(不是默认的vpn提供商,即,Android设置->无线和网络中存在的PPTP、L2TP等) 我想知道,从2017年起,这是否已经可行 这里有我使用的参考资料 在使用数据报通道时,我得到了一个PortUnreactableException。这就是我的代码的样子:Android:使用VPN服务的VPN连接,android,android-vpn-service,Android,Android Vpn Service,我正在尝试以编程方式建立并连接到我们自己的vpn(不是默认的vpn提供商,即,Android设置->无线和网络中存在的PPTP、L2TP等) 我想知道,从2017年起,这是否已经可行 这里有我使用的参考资料 在使用数据报通道时,我得到了一个PortUnreactableException。这就是我的代码的样子: @Override public void run() { try { Log.i(getTag(), "Starting"); // If
@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;
}
MyVPN服务:
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);
}
}
}
classmyvpnservice扩展了VpnService{
私有静态最终字符串标记=MyVpnService.class.getSimpleName();
私有线程mThread;
私人包裹面;
//a、 为接口配置生成器。
Builder=新的Builder();
public static final String ACTION_CONNECT=“com.example.android.toyvpn.START”;
public static final String ACTION_DISCONNECT=“com.example.android.toyvpn.STOP”;
私人经理人;
私有挂起内容MConfigureContent;
私有最终原子引用mConnectingThread=新原子引用();
私有最终原子引用mConnection=新原子引用();
私有AtomicInteger mNextConnectionId=新的AtomicInteger(1);
私有静态类连接扩展对{
公共连接(线程、包文件描述符pfd){
超级(螺纹,pfd);
}
}
@凌驾
public void onCreate(){
Log.e(“MyVpnService”、“onCreate”);
//处理程序仅用于显示消息。
if(mHandler==null){
mHandler=新处理程序();
}
//创建“配置”连接的意图(只需启动YVPNClient)。
mconfigureContent=pendingent.getActivity(this,0,newintent(this,ToyVpnClient.class)),
PendingEvent.FLAG_UPDATE_CURRENT);
}
//服务接口
@凌驾
公共int onStartCommand(Intent Intent、int标志、int startId){
//通过创建新线程启动新会话。
mThread=new Thread(new Runnable()){
@凌驾
公开募捐{
试一试{
//a、 配置TUN并获取接口。
mInterface=builder.setSession(“MyVPN服务”)
.addAddress(“192.168.0.1”,24)
.addDnsServer(“8.8.8.8”)
.addRoute(“0.0.0.0”,0).build();
//b、 要发送的数据包在此输入流中排队。
FileInputStream in=新的FileInputStream(
mInterface.getFileDescriptor());
//b、 接收到的数据包需要写入此输出流。
FileOutputStream out=新的FileOutputStream(
mInterface.getFileDescriptor());
//为单个数据包分配缓冲区。
ByteBuffer数据包=ByteBuffer.allocate(32767);
//c、 UDP通道可用于向服务器传递/从服务器获取ip包
DatagramChannel隧道=DatagramChannel.open();
//连接到服务器,localhost仅用于演示。
隧道连接(新的InetSocketAddress(“61.31.92.159”,1723));
//tunnel.connect(新的InetSocketAddress(“127.0.0.1”,8087));
//d、 保护此套接字,使其发送的包不会反馈到vpn服务。
保护(tunnel.socket());
//e、 使用循环传递数据包。
while(true){
//拿包进去
//将数据包放入隧道
//从隧道获取数据包
//返回不带输出的数据包
//睡觉是必须的
Log.e(“MyVpnService”、“true”);
睡眠(100);
}
}捕获(例外e){
//抓住任何例外
Log.e(标记“Exception:+e.toString());
e、 printStackTrace();
}最后{
试一试{
if(mInterface!=null){
mInterface.close();
mInterface=null;
}
}捕获(例外e){
Log.e(标记“Exception2:+e.toString());
}
}
}
},“MyVpnRunnable”);
//启动服务
mThread.start();
if(intent!=null&&ACTION_DISCONNECT.equals(intent.getAction())){
断开连接();
返回开始时间不粘;
}否则{
connect();
返回开始时间;
}
}
@凌驾
公共空间{
//TODO自动生成的方法存根
if(mThread!=null){
中断();
}
super.ondestory();
}
专用void connect(){
//成为前台服务。后台服务也可以是VPN服务,但它们可以
//在获得接收onRevoke()的机会之前,被背景检查扼杀。
updateForegroundNotification(R.string.connecting);
mHandler.sendEmptyMessage(R.string.connecting);
//从共享首选项中提取信息。
final SharedPreferences prefs=getSharedPreferences(ToyVpnClient.prefs.NAME,MODE_PRIVATE);
final String server=“61.31.92.159”//prefs.getString(ToyVpnClient.prefs.server_ADDRESS,”;
最终字节[]secret=“123456789.getBytes();/=prefs.getString(ToyVpnClient.prefs.SHARED_secret,”).getBytes();
最终国际端口;
试一试{
port=Integer.parseInt(“1723”);//Integer.parseInt(prefs.getString(ToyVpnClient.prefs.SERVER_port,”);
}捕获(数字格式){
Log.e(“MyVPN”,“坏端口:”+prefs.getString(ToyVpnClient.prefs.SERVER_port,null),e);
返回;
}
//启动连接。
startConnection(新TOYVPN连接(
这是,mNextConnectionId.getAndIncrement(),服务器,端口,机密);
}
私有无效断开连接(){
mHandler.sendEmptyMessage(R.string.disconnected);
SetConnectionRead(空);
设置连接(空);
停止前景(真);
}
私有void updateForeg
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);
}
}
}