diff --git a/app/src/main/java/com/pkusz/min_vpn_client/GlobalSetting.java b/app/src/main/java/com/pkusz/min_vpn_client/GlobalSetting.java index 5e6c83a..74dc591 100644 --- a/app/src/main/java/com/pkusz/min_vpn_client/GlobalSetting.java +++ b/app/src/main/java/com/pkusz/min_vpn_client/GlobalSetting.java @@ -5,8 +5,10 @@ package com.pkusz.min_vpn_client; public class GlobalSetting { // public static boolean openBiometric = false; - // 控制与VPNServer的对称加密通信方法,true表示使用sm4,false表示使用AES + // 控制内网中与VPNServer的对称加密通信方法,true表示使用sm4,false表示使用AES public static boolean isSM4 = true; + // 控制外网中与VPNServer的对称加密通信方法,暂时只支持SM4 + public static boolean isSM4InExternal = true; // 是否开启VPN服务器信息同步 public static boolean openSyncVPNInfo = false; diff --git a/app/src/main/java/com/pkusz/min_vpn_client/model/VpnServiceInfo.java b/app/src/main/java/com/pkusz/min_vpn_client/model/VpnServiceInfo.java new file mode 100644 index 0000000..396eca6 --- /dev/null +++ b/app/src/main/java/com/pkusz/min_vpn_client/model/VpnServiceInfo.java @@ -0,0 +1,12 @@ +package com.pkusz.min_vpn_client.model; +/* + * @Author: Wang Feng + * @Description: 存储路由配置的信息 + * 最最基础的字段:mtu addr dns + * @Version: 1.0.0 + * @Date: 10:04 2021/8/11 + * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 + */ + +public class VpnServiceInfo { +} diff --git a/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnConnection.java b/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnConnection.java new file mode 100644 index 0000000..1e35a0f --- /dev/null +++ b/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnConnection.java @@ -0,0 +1,29 @@ +package com.pkusz.min_vpn_client.services; +/* + * @Author: Wang Feng + * @Description: + * @Version: 1.0.0 + * @Date: 9:04 2021/8/11 + * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 + */ + +import android.app.PendingIntent; +import android.os.ParcelFileDescriptor; + +public interface MINVpnConnection extends Runnable{ + // 三种回调函数接口的声明 + interface OnEstablishListener { + void onEstablish(ParcelFileDescriptor tunInterface); + } + interface OnConnectFailedListener { + void onFailed(); + } + interface OnTimeoutFailedListener { + void onTimeout(); + } + // 共有方法 + void setConfigureIntent(PendingIntent intent); + void setOnEstablishListener(OnEstablishListener listener); + void setOnConnectFailedListener(OnConnectFailedListener listener); + void setOnTimeoutFailedListener(OnTimeoutFailedListener listener); +} diff --git a/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnExternalConnection.java b/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnExternalConnection.java index 9f0e899..45d1459 100644 --- a/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnExternalConnection.java +++ b/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnExternalConnection.java @@ -1,708 +1,637 @@ -//package com.pkusz.min_vpn_client.services; -///* -// * @Author: Wang Feng -// * @Description: 建立一条连接外网的VPN连接的逻辑。默认加密。 -// * @Version: 1.0.0 -// * @Date: 11:09 2021/7/13 -// * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 -// */ -// -//import android.app.PendingIntent; -//import android.content.pm.PackageManager; -//import android.net.ProxyInfo; -//import android.net.VpnService; -//import android.os.Build; -//import android.os.ParcelFileDescriptor; -//import android.text.TextUtils; -// -//import androidx.annotation.RequiresApi; -// -//import com.pkusz.min_vpn_client.GlobalSetting; -//import com.pkusz.min_vpn_client.model.MINVpnSettingAPI; -//import com.pkusz.min_vpn_client.model.Response.VpnServerInfo; -//import com.pkusz.min_vpn_client.utils.AESHelperForConnection; -//import com.pkusz.min_vpn_client.utils.IPPackageUtil; -//import com.pkusz.min_vpn_client.utils.KeyManager; -//import com.pkusz.min_vpn_client.utils.RuleUtil; -//import com.pkusz.min_vpn_client.utils.SM2HelperForConnection; -//import com.pkusz.min_vpn_client.utils.SM3HelperForConnection; -//import com.pkusz.min_vpn_client.utils.SM4HelperForConnection; -// -//import java.io.FileInputStream; -//import java.io.FileOutputStream; -//import java.io.IOException; -//import java.nio.ByteBuffer; -//import java.nio.charset.StandardCharsets; -//import java.util.Arrays; -//import java.util.Objects; -//import java.util.Set; -// -//import common.LoggerHelper; -//import component.ComponentException; -//import component.Identifier; -//import component.IdentifierComponent; -//import logicface.LogicFace; -//import logicface.LogicFaceException; -//import mgmt.RegisterPrefixHelper; -//import packet.CPacket; -//import packet.PacketException; -//import util.ByteHelper; -// -//import static java.nio.charset.StandardCharsets.US_ASCII; -// -//public class MINVpnExternalConnection implements Runnable{ -// private static long interestSeq = 0; // 兴趣包序号 -// private String handshakeResponse = null; // 握手应答 -// private ParcelFileDescriptor mParcelFileDescriptor = null; // 读出写入流 -// private boolean stop = false; // 标记本次连接是否停止 -// -// // 最大包长度 -// private static final int MAX_PACKET_SIZE = Short.MAX_VALUE; -// -// // VPNService、 连接ID、 本地服务前缀 -// private final VpnService mService; -// private final int mConnectionId; -// // face的注册前缀 格式为:/{边界路由器的服务前缀}/{用户名}/{当前系统时间}, -// // 如:"/min/gdcni19/wefree/"+System.currentTimeMillis(); -// // 在执行initFace的时候初始化 -// private String localFacePrefix; -// -// // 外网服务器地址、密钥 -// private VpnServerInfo mVpnServerInfo; -// private final byte[] mSharedSecret; -// private final String mSharedSecretString; -// private byte[] mEncryptedAESSeed; -// private boolean isSM4; -// private byte[] realSecret; // => sm4做对称加密时的真实密钥。由mSharedSecret经sm3哈希得到。 -// -// // 代理地址 todo:?? -// private String mProxyHostName; -// private int mProxyHostPort; -// -// // 权限控制 -// private final boolean mAllow; -// private final Set mPackages; -// -// // 服务器给客户端的tun网卡分配的IP地址 -// private String allocatedAddress; -// -// // 待决Intent、回调函数(建立连接成功;建立连接失败;长时间不操作的超时回调) -// private PendingIntent mConfigureIntent; -// private OnEstablishListener mOnEstablishListener; -// private OnConnectFailedListener mOnConnectFailedListener; -// private OnTimeoutFailedListener mOnTimeoutFailedListener; -// // 三种回调函数接口的声明 -// public interface OnEstablishListener { -// void onEstablish(ParcelFileDescriptor tunInterface); -// } -// public interface OnConnectFailedListener { -// void onFailed(); -// } -// public interface OnTimeoutFailedListener { -// void onTimeout(); -// } -// -// /** -// * 构造函数 -// * @param service -// * @param connectionId -// * @param vpnServerInfo -// * @param sharedSecret -// * @param proxyHostName -// * @param proxyHostPort -// * @param allow -// * @param packages -// * @param isSM4 => 是否使用sm4做对称加密通信。false时,则使用AES做对称加密。 -// */ -// @RequiresApi(api = Build.VERSION_CODES.O) -// public MINVpnExternalConnection(final VpnService service, final int connectionId, -// final VpnServerInfo vpnServerInfo, final byte[] sharedSecret, -// final String proxyHostName, final int proxyHostPort, boolean allow, -// final Set packages, -// final boolean isSM4) { -// // VPNService 及 连接ID -// mService=service; -// mConnectionId=connectionId; -// -// // 使用SM4做对称加密,还是AES做对称加密 -// this.isSM4=isSM4; -// -// // 服务器地址 及 密钥 -// mVpnServerInfo=vpnServerInfo; -// mSharedSecret=sharedSecret; -// mSharedSecretString=new String(mSharedSecret); -// System.out.println("mSharedSecretString: "+mSharedSecretString); -// mEncryptedAESSeed= SM2HelperForConnection.encryptWithSM2(mSharedSecretString); -// if(isSM4) { -// realSecret= SM3HelperForConnection.getSM3Hash16(mSharedSecret); -// System.out.println("对称加密方式为sm4,其实际加密密钥为: "+ new String(realSecret) -// +" , len: "+realSecret.length); -// } -// -// // 测试AES -//// System.out.println("!!!密钥:"+mSharedSecretString); -//// byte[] res=AESHelperForConnection.encryptWithAES(mSharedSecretString, -//// "ilovecodingilovecodingilovecodingilovecodingilovecodingilovecodingilovecodingilovecoding" + -//// "ilovecodingilovecodingsuygdygyegdw".getBytes(StandardCharsets.UTF_8)); -//// System.out.println("!!!: "+res); -//// String raw=AESHelperForConnection.decryptWithAES(mSharedSecretString,res); -//// System.out.println("!!!raw: "+raw); -// -// // 测试SM4 -//// try { -//// byte[] enc= Android_SM4.encrypt_ECB_Padding("1234567812345678".getBytes(StandardCharsets.UTF_8), -//// "wefree".getBytes(StandardCharsets.UTF_8)); -//// System.out.println("enc: "+Arrays.toString(enc)); -//// System.out.println("enc: "+new String(enc)); -//// byte[] dec= Android_SM4.decrypt_ECB_Padding("1234567812345678".getBytes(StandardCharsets.UTF_8), -//// enc); -//// System.out.println("dec: "+Arrays.toString(dec)); -//// System.out.println("dec: "+new String(dec)); -//// } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchProviderException -//// | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) { -//// e.printStackTrace(); -//// } -// -// // 代理地址(?) -// if(!TextUtils.isEmpty(proxyHostName)) { -// mProxyHostName = proxyHostName; -// } -// if(proxyHostPort>0) { -// mProxyHostPort = proxyHostPort; -// } -// -// // 权限控制 -// mAllow=allow; -// mPackages=packages; -// } -// -// // Intend进行activity之间的消息传递 -// public void setConfigureIntent(PendingIntent intent) { -// mConfigureIntent = intent; -// } -// // 三个回调函数(建立连接成功;建立连接失败;长时间不操作的超时回调) -// public void setOnEstablishListener(OnEstablishListener listener) { -// mOnEstablishListener = listener; -// } -// public void setOnConnectFailedListener(OnConnectFailedListener listener) { -// mOnConnectFailedListener = listener; -// } -// public void setOnTimeoutFailedListener(OnTimeoutFailedListener listener) { -// mOnTimeoutFailedListener = listener; -// } -// -// @RequiresApi(api = Build.VERSION_CODES.Q) -// @Override -// public void run() { -// // 1. 建立一条连接服务器的TCP-LogicFace -// LogicFace face=initTCPLogicFace(this.mServerName,this.mServerPort); -// if(face==null){ -// LoggerHelper.severe("vpn connection run error: tcp logic face init error"); -// return; -// } -// -// // 2. VPN握手,交换信息 -// boolean flag=handShake(face); -// if((this.mParcelFileDescriptor==null)||(!flag)){ -// LoggerHelper.severe("vpn connection run error: handle shake error"); -// return; -// } -// -// // 3. 进行数据中转操作 -// transferData(face); -// -// // 4. 开启心跳包,间隔时间为0.5s +package com.pkusz.min_vpn_client.services; +/* + * @Author: Wang Feng + * @Description: 建立一条连接外网的VPN连接的逻辑。默认加密。 + * @Version: 1.0.0 + * @Date: 11:09 2021/7/13 + * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 + */ + +import android.app.PendingIntent; +import android.content.pm.PackageManager; +import android.net.ProxyInfo; +import android.net.VpnService; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.text.TextUtils; + +import androidx.annotation.RequiresApi; + +import com.pkusz.min_vpn_client.GlobalSetting; +import com.pkusz.min_vpn_client.model.MINVpnSettingAPI; +import com.pkusz.min_vpn_client.model.Response.VpnServerInfo; +import com.pkusz.min_vpn_client.utils.AESHelperForConnection; +import com.pkusz.min_vpn_client.utils.IPPackageUtil; +import com.pkusz.min_vpn_client.utils.KeyManager; +import com.pkusz.min_vpn_client.utils.RuleUtil; +import com.pkusz.min_vpn_client.utils.SM2HelperForConnection; +import com.pkusz.min_vpn_client.utils.SM3HelperForConnection; +import com.pkusz.min_vpn_client.utils.SM4HelperForConnection; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; + +import common.LoggerHelper; +import component.ComponentException; +import component.Identifier; +import component.IdentifierComponent; +import logicface.LogicFace; +import logicface.LogicFaceException; +import mgmt.RegisterPrefixHelper; +import packet.CPacket; +import packet.PacketException; +import util.ByteHelper; + +import static java.nio.charset.StandardCharsets.US_ASCII; + +public class MINVpnExternalConnection implements MINVpnConnection{ + private static long interestSeq = 0; // 兴趣包序号 + private String handshakeResponse = null; // 握手应答 + private ParcelFileDescriptor mParcelFileDescriptor = null; // 读出写入流 + private boolean stop = false; // 标记本次连接是否停止 + + // 最大包长度 + private static final int MAX_PACKET_SIZE = Short.MAX_VALUE; + + // VPNService、 连接ID、 本地服务前缀 + private final VpnService mService; + private final int mConnectionId; + // face的注册前缀 格式为:/{边界路由器的服务前缀}/{用户名}/{当前系统时间}, + // 如:"/min/gdcni19/wefree/"+System.currentTimeMillis(); + // 在执行initFace的时候初始化 + private String localFacePrefix; + + // 外网服务器地址、密钥 + private VpnServerInfo mVpnServerInfo; + private final String mServerName; + private final int mServerPort; + private final byte[] mSharedSecret; + private final String mSharedSecretString; + private byte[] mEncryptedAESSeed; + private boolean isSM4; + private byte[] realSecret; // => sm4做对称加密时的真实密钥。由mSharedSecret经sm3哈希得到。 + + // 代理地址 todo:?? + private String mProxyHostName; + private int mProxyHostPort; + + // 权限控制 + private final boolean mAllow; + private final Set mPackages; + + // 服务器给客户端的tun网卡分配的IP地址 + // 由于外网访问的VPN服务器并不分配IP,这里的IP源自内网访问的VPN服务器分配的IP地址 + // 因此要执行外网访问程序,必须先至少与内网访问程序握手去获得IP。fuck it + private String allocatedAddress; + + // 待决Intent、回调函数(建立连接成功;建立连接失败;长时间不操作的超时回调) + private PendingIntent mConfigureIntent; + private OnEstablishListener mOnEstablishListener; + private OnConnectFailedListener mOnConnectFailedListener; + private OnTimeoutFailedListener mOnTimeoutFailedListener; + + /** + * 构造函数 + * @param service + * @param connectionId + * @param vpnServerInfo + * @param sharedSecret + * @param proxyHostName + * @param proxyHostPort + * @param allow + * @param packages + * @param isSM4 => 是否使用sm4做对称加密通信。false时,则使用AES做对称加密。 + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public MINVpnExternalConnection(final VpnService service, final int connectionId, + final VpnServerInfo vpnServerInfo, final byte[] sharedSecret, + final String proxyHostName, final int proxyHostPort, boolean allow, + final Set packages, + final boolean isSM4, + final String allocatedAddressFromInner) { + // VPNService 及 连接ID + mService=service; + mConnectionId=connectionId; + + // 使用SM4做对称加密,还是AES做对称加密 + this.isSM4=isSM4; + + // 内网给分配的IP地址 + this.allocatedAddress = allocatedAddressFromInner; + + // 服务器地址 及 密钥 + mVpnServerInfo=vpnServerInfo; + mServerName=mVpnServerInfo.IP; + mServerPort=mVpnServerInfo.Port; + mSharedSecret=sharedSecret; + mSharedSecretString=new String(mSharedSecret); + System.out.println("mSharedSecretString: "+mSharedSecretString); + mEncryptedAESSeed= SM2HelperForConnection.encryptWithSM2(mSharedSecretString); + if(isSM4) { + realSecret= SM3HelperForConnection.getSM3Hash16(mSharedSecret); + System.out.println("对称加密方式为sm4,其实际加密密钥为: "+ new String(realSecret) + +" , len: "+realSecret.length); + } + + // 代理地址(?) + if(!TextUtils.isEmpty(proxyHostName)) { + mProxyHostName = proxyHostName; + } + if(proxyHostPort>0) { + mProxyHostPort = proxyHostPort; + } + + // 权限控制 + mAllow=allow; + mPackages=packages; + } + + // Intend进行activity之间的消息传递 + @Override + public void setConfigureIntent(PendingIntent intent) { + mConfigureIntent = intent; + } + @Override + // 三个回调函数(建立连接成功;建立连接失败;长时间不操作的超时回调) + public void setOnEstablishListener(OnEstablishListener listener) { + mOnEstablishListener = listener; + } + @Override + public void setOnConnectFailedListener(OnConnectFailedListener listener) { + mOnConnectFailedListener = listener; + } + @Override + public void setOnTimeoutFailedListener(OnTimeoutFailedListener listener) { + mOnTimeoutFailedListener = listener; + } + + @RequiresApi(api = Build.VERSION_CODES.Q) + @Override + public void run() { + // 1. 建立一条连接服务器的TCP-LogicFace + LogicFace face=initTCPLogicFace(this.mServerName,this.mServerPort); + if(face==null){ + LoggerHelper.severe("vpn connection run error: tcp logic face init error"); + return; + } + + // 2. VPN握手,交换信息 + boolean flag=handShake(face); + if((this.mParcelFileDescriptor==null)||(!flag)){ + LoggerHelper.severe("vpn connection run error: handle shake error"); + return; + } + + // 3. 进行数据中转操作 + transferData(face); + + // 4. 开启心跳包,间隔时间为0.5s // startHeartBeatThread(face,500); -// } -// -// /** -// * 开启心跳包 -// * @param face -// * @param timeInterval 间隔多少毫秒 -// */ -// private void startHeartBeatThread(LogicFace face,int timeInterval){ -// Thread heartBeatThread = new Thread(() -> { -// // 1. 构造心跳包负载:{3}+{allocateIP}+{localPrefix} -// int len=1+allocatedAddress.length()+ localFacePrefix.length(); -// byte[] heartBeatPacketValue=new byte[len]; -// heartBeatPacketValue[0]=3; -// System.arraycopy(allocatedAddress.getBytes(StandardCharsets.UTF_8),0, -// heartBeatPacketValue,1,allocatedAddress.length()); -// System.arraycopy(localFacePrefix.getBytes(StandardCharsets.UTF_8),0, -// heartBeatPacketValue,1+allocatedAddress.length(), localFacePrefix.length()); -// -// // 2. 将heartBeatPacket通过face发送出去,每隔半秒发一次 -// while (!this.stop) { -// sendInterestPkt(face, ByteBuffer.wrap(heartBeatPacketValue), len); -// try { -// Thread.sleep(timeInterval); -// } catch (InterruptedException e) { -// e.printStackTrace(); -// System.out.println("heartbeat thread sleep error: "+e.getMessage()); -// } -// } -// }); -// heartBeatThread.start(); -// } -// -// /** -// * 双线程中转数据 -// * @param face -// * @return -// */ -// private boolean transferData(LogicFace face){ -// FileInputStream inputStream=new FileInputStream( -// this.mParcelFileDescriptor.getFileDescriptor()); -// FileOutputStream outputStream=new FileOutputStream( -// this.mParcelFileDescriptor.getFileDescriptor()); -// -// // -------------- 线程1:从tun网卡读出网络包,并往服务器发 ----------------- -// -// ByteBuffer buff1 = ByteBuffer.allocate(MAX_PACKET_SIZE); -// ByteBuffer buff2 = ByteBuffer.allocate(MAX_PACKET_SIZE); -// -// Thread recvFromTunAndSendThread = new Thread(() -> { -// while (!this.stop) { -// buff1.clear(); -// buff2.clear(); -// int readLen; -// int encryptedLen; -// // 1. 读取tun网卡数据,放入buff1 -// try { -// readLen = inputStream.read(buff1.array(), 0, MAX_PACKET_SIZE); -// } catch (Exception e) { -// e.printStackTrace(); -// break; -// } -// if (readLen > 0) { -// System.out.println("read from tun , readLen = " + readLen); -// buff1.position(0); -// buff1.limit(readLen); -// -// // 2. 对数据做AES加密,然后将服务器分配的地址[allocatedAddress]和加密数据一起放入buff2 -// buff2.put(Objects.requireNonNull(IPPackageUtil.StringAddress2byteAddress(allocatedAddress))); -// byte[] ipPackageData = new byte[readLen]; -// System.arraycopy(buff1.array(), 0, ipPackageData, 0, readLen); -// System.out.println("mSharedSecretString length: "+mSharedSecretString.length()); -//// System.out.println("realSecret length: "+realSecret.length); -// byte[] encrytedIPPackage; -// if(isSM4) { -// encrytedIPPackage = SM4HelperForConnection.encryptWithSM4(realSecret, ipPackageData); -// }else { -// encrytedIPPackage = AESHelperForConnection.encryptWithAES(mSharedSecretString, ipPackageData); -// } -// if(encrytedIPPackage==null){ -// System.out.println("对称加密错误!!! 对称加密是否为sm4: "+isSM4); -// return; -// } -// System.out.println("encryptedIPPackage len: " + encrytedIPPackage.length); -// System.out.println("encryptedIPPackage: "+ Arrays.toString(encrytedIPPackage)); -// System.out.println("buff2 position: " + buff2.position()); -// buff2.put(encrytedIPPackage); -// encryptedLen = buff2.position(); -// buff2.position(0); -// buff2.limit(encryptedLen); -// -// // 3. 将buff2通过face发送出去 -// sendInterestPkt(face, buff2,encryptedLen); -// } -// -// } -// }); -// recvFromTunAndSendThread.start(); -// -// // -------------- 线程2:从服务器接收网络包,并往网卡写 ----------------- -// Thread recvFromServerAndReadThread = new Thread(() -> { -// while (!this.stop) { -// try { -// // 1. 接收服务器返回的数据包,放入buf2 -// CPacket interest; -// try { -// interest = face.receiveCPacket(10000); -// }catch (Exception e){ -// continue; -// } -// System.out.println("recv interest packet, name : " + interest.getSrcIdentifier().toString()); -// System.out.println("兴趣包中装的数据长度 : " + interest.payload.getValue().length); -// int dataLen = interest.payload.getValue().length; -// ByteBuffer buf2 = ByteBuffer.allocate(dataLen); -// buf2.put(interest.payload.getValue()); -// System.out.println("buf2 data len = " + buf2.position()); -// -// // 2. 将buf2数据解密,然后放入buf3,通过tun网卡进行写出 -// byte[] decrpytedIPPackage = new byte[buf2.position()]; -// System.arraycopy(buf2.array(), 0, decrpytedIPPackage, 0, buf2.position()); -// ByteBuffer buf3 = ByteBuffer.allocate(5000); -//// System.out.println("要解密啦,要解密的包是:"+ Arrays.toString(decrpytedIPPackage)); -//// System.out.println("要解密啦,要解密的包长度是:"+decrpytedIPPackage.length); -// if(isSM4) { -// buf3.put(Objects.requireNonNull(SM4HelperForConnection.decryptWithSM4(realSecret, decrpytedIPPackage))); -// }else{ -// buf3.put(Objects.requireNonNull(AESHelperForConnection.decryptWithAES(mSharedSecretString, decrpytedIPPackage))); -// } -//// System.out.println("解密结果:"+ Arrays.toString(buf3.array())); -//// System.out.println("解密结果-len:"+ buf3.position()); -// int decryptedDataLen = buf3.position(); -// byte firstByte = buf3.get(0); -// if (firstByte == 0) { -// // 提示连接超时 -// mOnTimeoutFailedListener.onTimeout(); -// } else { -// try { -// outputStream.write(buf3.array(), 0, decryptedDataLen); -// } catch (IOException e) { -// e.printStackTrace(); -// System.out.println("transfer data error: outputStream.write error"); -// } -// } -// } catch (SecurityException e) { -// e.printStackTrace(); -// System.out.println("transfer data error: recvFromServerAndReadThread.start error"); -// } -// } -// }); -// recvFromServerAndReadThread.start(); -// -// return true; -// } -// -// /** -// * 向服务器转发数据 buf:ip&data -// * @param face -// * @param buf -// */ -// private void sendInterestPkt(LogicFace face, ByteBuffer buf,int bufLen) { -// // 1. 构造CPacket -// // 构造目的标识:/{service prefix}/{cPacket seq}/{defraudString} -// Identifier serverIdentifier; -// try { -// serverIdentifier = new Identifier(MINVpnSettingAPI.INSTANCE.getServicePrefix()); -// } catch (ComponentException e) { -// e.printStackTrace(); -// LoggerHelper.warning("sendInterestPkt error 1: serverIdentifier init error: "+e.getMessage()); -// return; -// } -// serverIdentifier.appendFragmentNumber(interestSeq++); -// serverIdentifier.append(new IdentifierComponent(MINVpnSettingAPI.defraudString)); -// // 构造本地标识:/{local_prefix} -// Identifier clientIdentifier; -// try { -//// clientIdentifier = new Identifier(MINVpnSettingAPI.INSTANCE.getLocalPrefix()); -// clientIdentifier = new Identifier(localFacePrefix); -// } catch (ComponentException e) { -// e.printStackTrace(); -// LoggerHelper.warning("sendInterestPkt error 1: clientIdentifier init error: "+e.getMessage()); -// return; -// } -// // 构造CPacket -// CPacket cPacket = new CPacket(); -// cPacket.setSrcIdentifier(clientIdentifier); -// cPacket.setDstIdentifier(serverIdentifier); -// cPacket.ttl.setTtl(64); -// // 从buf取出有效字段到writeBytes -// int writeLen=bufLen; -// byte[] writeBytes=new byte[writeLen]; -// System.arraycopy(buf.array(),0,writeBytes,0,writeLen); -// System.out.println("array: "+ Arrays.toString(buf.array())); -// System.out.println("write: "+ Arrays.toString(writeBytes)); -// // 放置到payload中 -// cPacket.payload.setValue(writeBytes); -// -// System.out.println("raw interest param size ============= " + cPacket.payload.getValue().length); -// -// // 2. 对兴趣包进行签名 -// try { -// System.out.println("当前身份名称: "+KeyManager.INSTANCE.getKeyChain().getCurrentIdentity().getName()); -// KeyManager.INSTANCE.getKeyChain().signCPacket(cPacket); -// System.out.println("签名字段: "+Arrays.toString(cPacket.minPacket.signatureField.getSignature(0) -// .getSigValue().getValue())); -// } catch (Exception error) { -// error.printStackTrace(); -// return; -// } -// -// System.out.println("signed interest param size ============= " + cPacket.payload.getValue().length); -// -// // 3. 将兴趣包发送到服务器 -// try { -// face.sendCPacket(cPacket); -// } catch (LogicFaceException e) { -// e.printStackTrace(); -// System.out.println("vpn connection sendInterestPkt error: send interest error: "+e.getMessage()); -// return; -// } -// -// try { -// System.out.println("express interest : " +cPacket.getSrcIdentifier().toUri() -// +" -> "+cPacket.getDstIdentifier().toUri()); -// } catch (ComponentException e) { -// e.printStackTrace(); -// System.out.println("express interest parse uri error" ); -// } -// } -// -// /** -// * VPN握手,交换信息。 -// * @param face -// */ -// @RequiresApi(api = Build.VERSION_CODES.Q) -// private boolean handShake(LogicFace face){ -// // 1. 构造兴趣包 -// // 构造兴趣包负载字段:加密密钥 & 本地前缀 -// ByteBuffer requestBuf = ByteBuffer.allocate(1024); -// byte len= ByteHelper.uint8ToByte(mEncryptedAESSeed.length); -// if(!GlobalSetting.isSM4) { -// requestBuf.put((byte) 4).put(len).put(mEncryptedAESSeed).put(localFacePrefix.getBytes()).flip(); -// }else{ -// requestBuf.put((byte) 1).put(len).put(mEncryptedAESSeed).put(localFacePrefix.getBytes()).flip(); -// } -// // 构造目的标识:/{service prefix}/{cPacket seq}/{defraudString} -// Identifier serverIdentifier; -// try { -// serverIdentifier = new Identifier(MINVpnSettingAPI.INSTANCE.getServicePrefix()); -// } catch (ComponentException e) { -// e.printStackTrace(); -// LoggerHelper.warning("handshake error 1: serverIdentifier init error: "+e.getMessage()); -// return false; -// } -// serverIdentifier.appendFragmentNumber(interestSeq++); -// serverIdentifier.append(new IdentifierComponent(MINVpnSettingAPI.defraudString)); -// // 构造本地标识:/{local_prefix} -// Identifier clientIdentifier; -// try { -//// clientIdentifier = new Identifier(MINVpnSettingAPI.INSTANCE.getLocalPrefix()); -// clientIdentifier = new Identifier(localFacePrefix); -// } catch (ComponentException e) { -// e.printStackTrace(); -// LoggerHelper.warning("handshake error 1: clientIdentifier init error: "+e.getMessage()); -// return false; -// } -// // 构造CPacket:名称 生存时间 是否要求最新 TTL 请求内容 -// CPacket cPacket = new CPacket(); -// System.out.println("src是什么:"+clientIdentifier.toString()); -// try { -// System.out.println("src是什么:"+clientIdentifier.toUri()); -// } catch (ComponentException e) { -// e.printStackTrace(); -// } -// try { -// System.out.println("src是什么:"+clientIdentifier.toUriTemp()); -// } catch (ComponentException e) { -// e.printStackTrace(); -// } -// System.out.println("dst是什么:"+serverIdentifier.toString()); -// try { -// System.out.println("dst是什么:"+serverIdentifier.toUri()); -// } catch (ComponentException e) { -// e.printStackTrace(); -// } -// cPacket.setSrcIdentifier(clientIdentifier); -// cPacket.setDstIdentifier(serverIdentifier); -// cPacket.ttl.setTtl(64); -// // 从requestBuf取出有效字段到writeBytes -// int writeLen=1+1+mEncryptedAESSeed.length+ localFacePrefix.length(); -// byte[] writeBytes=new byte[writeLen]; -// System.arraycopy(requestBuf.array(),0,writeBytes,0,writeLen); -// System.out.println("array: "+ Arrays.toString(requestBuf.array())); -// System.out.println("write: "+ Arrays.toString(writeBytes)); -// // 放置到payload中 -// cPacket.payload.setValue(writeBytes); -// -// // 输出未签名兴趣包 -// try { -// System.out.println("express origin Interest: " + cPacket.toUri()); -// } catch (PacketException e) { -// e.printStackTrace(); -// System.out.println("express origin Interest: error"); -// } -// -// // 2. 对兴趣包进行签名 -// try { -// System.out.println("当前身份名称: "+KeyManager.INSTANCE.getKeyChain().getCurrentIdentity().getName()); -// KeyManager.INSTANCE.getKeyChain().signCPacket(cPacket); -// System.out.println("签名字段: "+Arrays.toString(cPacket.minPacket.signatureField.getSignature(0) -// .getSigValue().getValue())); -// } catch (Exception error) { -// error.printStackTrace(); -// LoggerHelper.warning("handshake error 2: keychain sign error: "+error.getMessage()); -// return false; -// } -// -// // 输出签名兴趣包 -// try { -// System.out.println("express signed Interest: " + cPacket.toUri()); -// } catch (PacketException e) { -// e.printStackTrace(); -// System.out.println("express signed Interest: error"); -// } -// -// // 3. 发送兴趣包到服务器,接收服务器发回来的数据包 -// try { -// face.sendCPacket(cPacket); -// CPacket data=face.receiveCPacket(10000); -// ByteBuffer respondBuf = ByteBuffer.allocate(1024); -// respondBuf.put(data.payload.getValue()); -// System.out.println("handshake recv data , data content : " + new String(data.payload.getValue())); -// // 应答信息:m,8000 a,10.254.0.2 d,114.114.114.114 r,172.16.0.0,12 r,10.0.0.0,16 r,192.168.0.0,16 -// this.handshakeResponse = new String(respondBuf.array(), 0, data.payload.getValue().length, US_ASCII).trim(); -// } catch (LogicFaceException e) { -// e.printStackTrace(); -// LoggerHelper.warning("handshake error 3: send sign cPacket error: "+e.getMessage()); -// return false; -// } -// -// // 4. 分析服务器的数据包应答,根据其中信息进行本地的路由配置 -// System.out.println("handshake result : " + this.handshakeResponse); -// this.mParcelFileDescriptor = configure(this.handshakeResponse); -// return true; -// } -// -// /** -// * 握手的最后一步,根据服务器的返回信息对本地路由进行配置 -// * @param parameters -// * @return -// * @throws IllegalArgumentException -// */ -// @RequiresApi(api = Build.VERSION_CODES.Q) -// private ParcelFileDescriptor configure(String parameters) throws IllegalArgumentException { -// // 字符串有效性判断 -// if(parameters == null || parameters.equals("")) { -// mOnConnectFailedListener.onFailed(); -// return null; -// } -// // 1. 分解字符串从而获得VPN配置信息,并配置路由 -// VpnService.Builder builder = mService.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], 24); -//// builder.addAddress(fields[1], Integer.parseInt(fields[2])); -// System.out.println("handshake ip: " + fields[1]); -// allocatedAddress = fields[1]; -// break; -// case 'r': -// // 服务器配置 -// if(MINVpnSettingAPI.INSTANCE.getProxyMode() == 2) { -// builder.addRoute(fields[1], Integer.parseInt(fields[2])); -// } -// break; -// case 'd': -// builder.addDnsServer(fields[1]); -// break; -// case 's': -// builder.addSearchDomain(fields[1]); -// break; -// } -// } catch (NumberFormatException e) { -// throw new IllegalArgumentException("Bad parameter: " + parameter); -// } -// } -// // 规则 -// if(MINVpnSettingAPI.INSTANCE.getProxyMode() == 0) { -// configRoute(builder); -// } -// // 全局 -// if(MINVpnSettingAPI.INSTANCE.getProxyMode() == 1) { -// builder.addRoute("0.0.0.0",0); -// } -// -// // 2. 创建一个接口 -// final ParcelFileDescriptor vpnInterface; -// // 2.1 添加没有权限的目录(copy旧项目的,不知道这个目录指的哪个玩意儿) -// try { -// builder.addDisallowedApplication("com.example.androidvpn2"); -// } catch (PackageManager.NameNotFoundException e) { -// e.printStackTrace(); -// } -// // 2.2 添加传递进来的有权限或没权限的应用目录 -// for (String packageName : mPackages) { -// try { -// if (mAllow) { -// builder.addAllowedApplication(packageName); -// } else { -// builder.addDisallowedApplication(packageName); -// } -// } catch (PackageManager.NameNotFoundException e) { -// LoggerHelper.warning(getTag()+ " Package not available: " + packageName+ e); -// } -// } -// // 2.3 设置server和proxy的地址,以及intent -// builder.setSession(mVpnServerInfo.IP).setConfigureIntent(mConfigureIntent); -// if (!TextUtils.isEmpty(mProxyHostName)) { -// builder.setHttpProxy(ProxyInfo.buildDirectProxy(mProxyHostName, mProxyHostPort)); -// } -// // 2.4 根据前面配置的builder创建一个vpn句柄 -// synchronized (mService) { -// vpnInterface = builder.establish(); -// if (mOnEstablishListener != null) { -// mOnEstablishListener.onEstablish(vpnInterface); -// } -// } -// LoggerHelper.finer(getTag()+ " New interface: " + vpnInterface + " (" + parameters + ")"); -// return vpnInterface; -// } -// -// // 输出提示信息时使用 -// private final String getTag() { -// return MINVpnExternalConnection.class.getSimpleName() + "[" + mConnectionId + "]"; -// } -// -// /** -// * 根据builder进行规则路由配置 -// * @param builder -// */ -// private void configRoute(VpnService.Builder builder) { -// String[] routes = RuleUtil.getRules(mService); -// for(int i = 0; i < routes.length; i++) { -// String route = routes[i]; -// String[] fields = route.split("/"); -// //System.out.println(fields[0]); -// builder.addRoute(fields[0], Integer.parseInt(fields[1])); -// } -// } -// -// /** -// * 生成一个连接指定服务器地址的TCP LogicFace,并进行标识注册 -// * @param hostIP -// * @param hostPort -// * @return -// */ -// private LogicFace initTCPLogicFace(String hostIP,Integer hostPort){ -// System.out.println("host ip = " + hostIP + ", host port = " + hostPort); -// // 1. 初始化TCP Face到MIN边界路由器 -// LogicFace face; -// try { -// face = new LogicFace(); -// face.initWithTcp(hostIP,hostPort); -// } catch (LogicFaceException e) { -// LoggerHelper.warning("tcp logic face init error: "+e.getMessage()); -// return null; -// } -// // 2. 注册标识 -// RegisterPrefixHelper helper=new RegisterPrefixHelper(); -// try { -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { -// localFacePrefix =MINVpnSettingAPI.INSTANCE.getFirstRouterPrefix() -//// +"/"+Base64Util.Base64PlusEncode(MINVpnSettingAPI.INSTANCE.getUsername()) -// +"/"+MINVpnSettingAPI.INSTANCE.getUsername() -// +"/"+System.currentTimeMillis(); -// }else { -// System.out.println("Base64Util.Base64PlusDecode error"); -// return null; -// } -// System.out.println("注册前缀:"+ localFacePrefix); -// face.setKeyChain(KeyManager.INSTANCE.getKeyChain()); -// face.registerIdentifier(new Identifier(localFacePrefix),5000,helper); -// } catch (LogicFaceException | ComponentException e) { -// e.printStackTrace(); -// LoggerHelper.warning("tcp logic face register identifier error: "+e.getMessage()); -// return null; -// } -// return face; -// } -//} + } + + /** + * 开启心跳包 + * @param face + * @param timeInterval 间隔多少毫秒 + */ + private void startHeartBeatThread(LogicFace face,int timeInterval){ + Thread heartBeatThread = new Thread(() -> { + // 1. 构造心跳包负载:{3}+{allocateIP}+{localPrefix} + int len=1+allocatedAddress.length()+ localFacePrefix.length(); + byte[] heartBeatPacketValue=new byte[len]; + heartBeatPacketValue[0]=3; + System.arraycopy(allocatedAddress.getBytes(StandardCharsets.UTF_8),0, + heartBeatPacketValue,1,allocatedAddress.length()); + System.arraycopy(localFacePrefix.getBytes(StandardCharsets.UTF_8),0, + heartBeatPacketValue,1+allocatedAddress.length(), localFacePrefix.length()); + + // 2. 将heartBeatPacket通过face发送出去,每隔半秒发一次 + while (!this.stop) { + sendInterestPkt(face, ByteBuffer.wrap(heartBeatPacketValue), len); + try { + Thread.sleep(timeInterval); + } catch (InterruptedException e) { + e.printStackTrace(); + System.out.println("heartbeat thread sleep error: "+e.getMessage()); + } + } + }); + heartBeatThread.start(); + } + + /** + * 双线程中转数据 + * @param face + * @return + */ + private boolean transferData(LogicFace face){ + FileInputStream inputStream=new FileInputStream( + this.mParcelFileDescriptor.getFileDescriptor()); + FileOutputStream outputStream=new FileOutputStream( + this.mParcelFileDescriptor.getFileDescriptor()); + + // -------------- 线程1:从tun网卡读出网络包,并往服务器发 ----------------- + + ByteBuffer buff1 = ByteBuffer.allocate(MAX_PACKET_SIZE); + ByteBuffer buff2 = ByteBuffer.allocate(MAX_PACKET_SIZE); + + Thread recvFromTunAndSendThread = new Thread(() -> { + while (!this.stop) { + buff1.clear(); + buff2.clear(); + int readLen; + int encryptedLen; + // 1. 读取tun网卡数据,放入buff1 + try { + readLen = inputStream.read(buff1.array(), 0, MAX_PACKET_SIZE); + } catch (Exception e) { + e.printStackTrace(); + break; + } + if (readLen > 0) { + System.out.println("read from tun , readLen = " + readLen); + buff1.position(0); + buff1.limit(readLen); + + // 2. 对数据做AES加密,然后将服务器分配的地址[allocatedAddress]和加密数据一起放入buff2 + buff2.put(Objects.requireNonNull(IPPackageUtil.StringAddress2byteAddress(allocatedAddress))); + byte[] ipPackageData = new byte[readLen]; + System.arraycopy(buff1.array(), 0, ipPackageData, 0, readLen); + System.out.println("mSharedSecretString length: "+mSharedSecretString.length()); +// System.out.println("realSecret length: "+realSecret.length); + byte[] encrytedIPPackage; + if(isSM4) { + encrytedIPPackage = SM4HelperForConnection.encryptWithSM4(realSecret, ipPackageData); + }else { + encrytedIPPackage = AESHelperForConnection.encryptWithAES(mSharedSecretString, ipPackageData); + } + if(encrytedIPPackage==null){ + System.out.println("对称加密错误!!! 对称加密是否为sm4: "+isSM4); + return; + } + System.out.println("encryptedIPPackage len: " + encrytedIPPackage.length); + System.out.println("encryptedIPPackage: "+ Arrays.toString(encrytedIPPackage)); + System.out.println("buff2 position: " + buff2.position()); + buff2.put(encrytedIPPackage); + encryptedLen = buff2.position(); + buff2.position(0); + buff2.limit(encryptedLen); + + // 3. 将buff2通过face发送出去 + sendInterestPkt(face, buff2,encryptedLen); + } + + } + }); + recvFromTunAndSendThread.start(); + + // -------------- 线程2:从服务器接收网络包,并往网卡写 ----------------- + Thread recvFromServerAndReadThread = new Thread(() -> { + while (!this.stop) { + try { + // 1. 接收服务器返回的数据包,放入buf2 + CPacket interest; + try { + interest = face.receiveCPacket(10000); + }catch (Exception e){ + continue; + } + System.out.println("recv interest packet, name : " + interest.getSrcIdentifier().toString()); + System.out.println("兴趣包中装的数据长度 : " + interest.payload.getValue().length); + int dataLen = interest.payload.getValue().length; + ByteBuffer buf2 = ByteBuffer.allocate(dataLen); + buf2.put(interest.payload.getValue()); + System.out.println("buf2 data len = " + buf2.position()); + + // 2. 将buf2数据解密,然后放入buf3,通过tun网卡进行写出 + byte[] decrpytedIPPackage = new byte[buf2.position()]; + System.arraycopy(buf2.array(), 0, decrpytedIPPackage, 0, buf2.position()); + ByteBuffer buf3 = ByteBuffer.allocate(5000); +// System.out.println("要解密啦,要解密的包是:"+ Arrays.toString(decrpytedIPPackage)); +// System.out.println("要解密啦,要解密的包长度是:"+decrpytedIPPackage.length); + if(isSM4) { + buf3.put(Objects.requireNonNull(SM4HelperForConnection.decryptWithSM4(realSecret, decrpytedIPPackage))); + }else{ + buf3.put(Objects.requireNonNull(AESHelperForConnection.decryptWithAES(mSharedSecretString, decrpytedIPPackage))); + } +// System.out.println("解密结果:"+ Arrays.toString(buf3.array())); +// System.out.println("解密结果-len:"+ buf3.position()); + int decryptedDataLen = buf3.position(); + byte firstByte = buf3.get(0); + if (firstByte == 0) { + // 提示连接超时 + mOnTimeoutFailedListener.onTimeout(); + } else { + try { + outputStream.write(buf3.array(), 0, decryptedDataLen); + } catch (IOException e) { + e.printStackTrace(); + System.out.println("transfer data error: outputStream.write error"); + } + } + } catch (SecurityException e) { + e.printStackTrace(); + System.out.println("transfer data error: recvFromServerAndReadThread.start error"); + } + } + }); + recvFromServerAndReadThread.start(); + + return true; + } + + /** + * 向服务器转发数据 buf:ip&data + * @param face + * @param buf + */ + private void sendInterestPkt(LogicFace face, ByteBuffer buf,int bufLen) { + // 1. 构造CPacket + // 构造目的标识:/{service prefix}/{cPacket seq}/{defraudString} + Identifier serverIdentifier; + try { + serverIdentifier = new Identifier(MINVpnSettingAPI.INSTANCE.getServicePrefix()); + } catch (ComponentException e) { + e.printStackTrace(); + LoggerHelper.warning("sendInterestPkt error 1: serverIdentifier init error: "+e.getMessage()); + return; + } + serverIdentifier.appendFragmentNumber(interestSeq++); + serverIdentifier.append(new IdentifierComponent(MINVpnSettingAPI.defraudString)); + // 构造本地标识:/{local_prefix} + Identifier clientIdentifier; + try { +// clientIdentifier = new Identifier(MINVpnSettingAPI.INSTANCE.getLocalPrefix()); + clientIdentifier = new Identifier(localFacePrefix); + } catch (ComponentException e) { + e.printStackTrace(); + LoggerHelper.warning("sendInterestPkt error 1: clientIdentifier init error: "+e.getMessage()); + return; + } + // 构造CPacket + CPacket cPacket = new CPacket(); + cPacket.setSrcIdentifier(clientIdentifier); + cPacket.setDstIdentifier(serverIdentifier); + cPacket.ttl.setTtl(64); + // 从buf取出有效字段到writeBytes + int writeLen=bufLen; + byte[] writeBytes=new byte[writeLen]; + System.arraycopy(buf.array(),0,writeBytes,0,writeLen); + System.out.println("array: "+ Arrays.toString(buf.array())); + System.out.println("write: "+ Arrays.toString(writeBytes)); + // 放置到payload中 + cPacket.payload.setValue(writeBytes); + + System.out.println("raw interest param size ============= " + cPacket.payload.getValue().length); + + // 2. 对兴趣包进行签名 + try { + System.out.println("当前身份名称: "+KeyManager.INSTANCE.getKeyChain().getCurrentIdentity().getName()); + KeyManager.INSTANCE.getKeyChain().signCPacket(cPacket); + System.out.println("签名字段: "+Arrays.toString(cPacket.minPacket.signatureField.getSignature(0) + .getSigValue().getValue())); + } catch (Exception error) { + error.printStackTrace(); + return; + } + + System.out.println("signed interest param size ============= " + cPacket.payload.getValue().length); + + // 3. 将兴趣包发送到服务器 + try { + face.sendCPacket(cPacket); + } catch (LogicFaceException e) { + e.printStackTrace(); + System.out.println("vpn connection sendInterestPkt error: send interest error: "+e.getMessage()); + return; + } + + try { + System.out.println("express interest : " +cPacket.getSrcIdentifier().toUri() + +" -> "+cPacket.getDstIdentifier().toUri()); + } catch (ComponentException e) { + e.printStackTrace(); + System.out.println("express interest parse uri error" ); + } + } + + /** + * VPN握手,交换信息。 + * @param face + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + private boolean handShake(LogicFace face){ + // 1. 构造兴趣包 + // 构造兴趣包负载字段:加密密钥 & 本地前缀 + ByteBuffer requestBuf = ByteBuffer.allocate(1024); + byte len= ByteHelper.uint8ToByte(mEncryptedAESSeed.length); + if(this.isSM4) { + requestBuf.put((byte) 2).put(len).put(mEncryptedAESSeed) + .put(allocatedAddress.getBytes(StandardCharsets.UTF_8)) + .put(localFacePrefix.getBytes()).flip(); + }else{ + System.out.println("握手失败:外网访问只允许SM4通信加密!!!"); + return false; + } + // 构造目的标识:/{service prefix}/{cPacket seq}/{defraudString} + Identifier serverIdentifier; + try { + serverIdentifier = new Identifier(MINVpnSettingAPI.INSTANCE.getServicePrefix()); + } catch (ComponentException e) { + e.printStackTrace(); + LoggerHelper.warning("handshake error 1: serverIdentifier init error: "+e.getMessage()); + return false; + } + serverIdentifier.appendFragmentNumber(interestSeq++); + serverIdentifier.append(new IdentifierComponent(MINVpnSettingAPI.defraudString)); + // 构造本地标识:/{local_prefix} + Identifier clientIdentifier; + try { +// clientIdentifier = new Identifier(MINVpnSettingAPI.INSTANCE.getLocalPrefix()); + clientIdentifier = new Identifier(localFacePrefix); + } catch (ComponentException e) { + e.printStackTrace(); + LoggerHelper.warning("handshake error 1: clientIdentifier init error: "+e.getMessage()); + return false; + } + // 构造CPacket:名称 生存时间 是否要求最新 TTL 请求内容 + CPacket cPacket = new CPacket(); + System.out.println("src是什么:"+clientIdentifier.toString()); + try { + System.out.println("src是什么:"+clientIdentifier.toUri()); + } catch (ComponentException e) { + e.printStackTrace(); + } + try { + System.out.println("src是什么:"+clientIdentifier.toUriTemp()); + } catch (ComponentException e) { + e.printStackTrace(); + } + System.out.println("dst是什么:"+serverIdentifier.toString()); + try { + System.out.println("dst是什么:"+serverIdentifier.toUri()); + } catch (ComponentException e) { + e.printStackTrace(); + } + cPacket.setSrcIdentifier(clientIdentifier); + cPacket.setDstIdentifier(serverIdentifier); + cPacket.ttl.setTtl(64); + // 从requestBuf取出有效字段到writeBytes + int writeLen=1+1+mEncryptedAESSeed.length+ localFacePrefix.length(); + byte[] writeBytes=new byte[writeLen]; + System.arraycopy(requestBuf.array(),0,writeBytes,0,writeLen); + System.out.println("array: "+ Arrays.toString(requestBuf.array())); + System.out.println("write: "+ Arrays.toString(writeBytes)); + // 放置到payload中 + cPacket.payload.setValue(writeBytes); + + // 输出未签名兴趣包 + try { + System.out.println("express origin Interest: " + cPacket.toUri()); + } catch (PacketException e) { + e.printStackTrace(); + System.out.println("express origin Interest: error"); + } + + // 2. 对兴趣包进行签名 + try { + System.out.println("当前身份名称: "+KeyManager.INSTANCE.getKeyChain().getCurrentIdentity().getName()); + KeyManager.INSTANCE.getKeyChain().signCPacket(cPacket); + System.out.println("签名字段: "+Arrays.toString(cPacket.minPacket.signatureField.getSignature(0) + .getSigValue().getValue())); + } catch (Exception error) { + error.printStackTrace(); + LoggerHelper.warning("handshake error 2: keychain sign error: "+error.getMessage()); + return false; + } + + // 输出签名兴趣包 + try { + System.out.println("express signed Interest: " + cPacket.toUri()); + } catch (PacketException e) { + e.printStackTrace(); + System.out.println("express signed Interest: error"); + } + + // 3. 发送兴趣包到服务器,接收服务器发回来的数据包 + try { + face.sendCPacket(cPacket); + CPacket data=face.receiveCPacket(10000); + ByteBuffer respondBuf = ByteBuffer.allocate(1024); + respondBuf.put(data.payload.getValue()); + System.out.println("handshake recv data , data content : " + new String(data.payload.getValue())); + // 应答信息:m,8000 a,10.254.0.2 d,114.114.114.114 r,172.16.0.0,12 r,10.0.0.0,16 r,192.168.0.0,16 + this.handshakeResponse = new String(respondBuf.array(), 0, data.payload.getValue().length, US_ASCII).trim(); + } catch (LogicFaceException e) { + e.printStackTrace(); + LoggerHelper.warning("handshake error 3: send sign cPacket error: "+e.getMessage()); + return false; + } + + // 4. 分析服务器的数据包应答,根据其中信息进行本地的路由配置 + System.out.println("handshake result : " + this.handshakeResponse); + this.mParcelFileDescriptor = configure(); + return true; + } + + /** + * 握手的最后一步,对本地路由进行配置:外网访问直接配置0.0.0.0的默认路由 + * @return + * @throws IllegalArgumentException + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + private ParcelFileDescriptor configure() throws IllegalArgumentException { + // 1. 配置全局路由 + VpnService.Builder builder = mService.new Builder(); + builder.setMtu(8000); + builder.addAddress(allocatedAddress,24); + builder.addRoute("0.0.0.0",0); + builder.addDnsServer("114.114.114.114"); + + // 2. 创建一个接口 + final ParcelFileDescriptor vpnInterface; + // 2.1 添加没有权限的目录(copy旧项目的,不知道这个目录指的哪个玩意儿) + try { + builder.addDisallowedApplication("com.example.androidvpn2"); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + // 2.2 添加传递进来的有权限或没权限的应用目录 + for (String packageName : mPackages) { + try { + if (mAllow) { + builder.addAllowedApplication(packageName); + } else { + builder.addDisallowedApplication(packageName); + } + } catch (PackageManager.NameNotFoundException e) { + LoggerHelper.warning(getTag()+ " Package not available: " + packageName+ e); + } + } + // 2.3 设置server和proxy的地址,以及intent + builder.setSession(mServerName).setConfigureIntent(mConfigureIntent); + if (!TextUtils.isEmpty(mProxyHostName)) { + builder.setHttpProxy(ProxyInfo.buildDirectProxy(mProxyHostName, mProxyHostPort)); + } + // 2.4 根据前面配置的builder创建一个vpn句柄 + synchronized (mService) { + vpnInterface = builder.establish(); + if (mOnEstablishListener != null) { + mOnEstablishListener.onEstablish(vpnInterface); + } + } + LoggerHelper.finer(getTag()+ " New interface: " + vpnInterface + " (" + "全局路由" + ")"); + return vpnInterface; + } + + // 输出提示信息时使用 + private final String getTag() { + return MINVpnExternalConnection.class.getSimpleName() + "[" + mConnectionId + "]"; + } + + /** + * 生成一个连接指定服务器地址的TCP LogicFace,并进行标识注册 + * @param hostIP + * @param hostPort + * @return + */ + private LogicFace initTCPLogicFace(String hostIP,Integer hostPort){ + System.out.println("host ip = " + hostIP + ", host port = " + hostPort); + // 1. 初始化TCP Face到MIN边界路由器 + LogicFace face; + try { + face = new LogicFace(); + face.initWithTcp(hostIP,hostPort); + } catch (LogicFaceException e) { + LoggerHelper.warning("tcp logic face init error: "+e.getMessage()); + return null; + } + // 2. 注册标识 + RegisterPrefixHelper helper=new RegisterPrefixHelper(); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + localFacePrefix =MINVpnSettingAPI.INSTANCE.getFirstRouterPrefix() +// +"/"+Base64Util.Base64PlusEncode(MINVpnSettingAPI.INSTANCE.getUsername()) + +"/"+MINVpnSettingAPI.INSTANCE.getUsername() + +"/"+System.currentTimeMillis(); + }else { + System.out.println("Base64Util.Base64PlusDecode error"); + return null; + } + System.out.println("注册前缀:"+ localFacePrefix); + face.setKeyChain(KeyManager.INSTANCE.getKeyChain()); + face.registerIdentifier(new Identifier(localFacePrefix),5000,helper); + } catch (LogicFaceException | ComponentException e) { + e.printStackTrace(); + LoggerHelper.warning("tcp logic face register identifier error: "+e.getMessage()); + return null; + } + return face; + } +} diff --git a/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnInnerConnection.java b/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnInnerConnection.java index 40cfd50..d8416e8 100644 --- a/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnInnerConnection.java +++ b/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnInnerConnection.java @@ -49,7 +49,7 @@ import util.ByteHelper; import static java.nio.charset.StandardCharsets.US_ASCII; -public class MINVpnInnerConnection implements Runnable{ +public class MINVpnInnerConnection implements MINVpnConnection{ private static long interestSeq = 0; // 兴趣包序号 private String handshakeResponse = null; // 握手应答 private ParcelFileDescriptor mParcelFileDescriptor = null; // 读出写入流 @@ -91,16 +91,6 @@ public class MINVpnInnerConnection implements Runnable{ private OnEstablishListener mOnEstablishListener; private OnConnectFailedListener mOnConnectFailedListener; private OnTimeoutFailedListener mOnTimeoutFailedListener; - // 三种回调函数接口的声明 - public interface OnEstablishListener { - void onEstablish(ParcelFileDescriptor tunInterface); - } - public interface OnConnectFailedListener { - void onFailed(); - } - public interface OnTimeoutFailedListener { - void onTimeout(); - } /** * 构造函数 @@ -141,30 +131,6 @@ public class MINVpnInnerConnection implements Runnable{ +" , len: "+realSecret.length); } - // 测试AES -// System.out.println("!!!密钥:"+mSharedSecretString); -// byte[] res=AESHelperForConnection.encryptWithAES(mSharedSecretString, -// "ilovecodingilovecodingilovecodingilovecodingilovecodingilovecodingilovecodingilovecoding" + -// "ilovecodingilovecodingsuygdygyegdw".getBytes(StandardCharsets.UTF_8)); -// System.out.println("!!!: "+res); -// String raw=AESHelperForConnection.decryptWithAES(mSharedSecretString,res); -// System.out.println("!!!raw: "+raw); - - // 测试SM4 -// try { -// byte[] enc= Android_SM4.encrypt_ECB_Padding("1234567812345678".getBytes(StandardCharsets.UTF_8), -// "wefree".getBytes(StandardCharsets.UTF_8)); -// System.out.println("enc: "+Arrays.toString(enc)); -// System.out.println("enc: "+new String(enc)); -// byte[] dec= Android_SM4.decrypt_ECB_Padding("1234567812345678".getBytes(StandardCharsets.UTF_8), -// enc); -// System.out.println("dec: "+Arrays.toString(dec)); -// System.out.println("dec: "+new String(dec)); -// } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchProviderException -// | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) { -// e.printStackTrace(); -// } - // 代理地址(?) if(!TextUtils.isEmpty(proxyHostName)) { mProxyHostName = proxyHostName; @@ -179,6 +145,7 @@ public class MINVpnInnerConnection implements Runnable{ } // Intend进行activity之间的消息传递 + @Override public void setConfigureIntent(PendingIntent intent) { mConfigureIntent = intent; } @@ -204,7 +171,7 @@ public class MINVpnInnerConnection implements Runnable{ } // 2. VPN握手,交换信息 - boolean flag=handShake(face); + boolean flag=handShake(face,true); if((this.mParcelFileDescriptor==null)||(!flag)){ LoggerHelper.severe("vpn connection run error: handle shake error"); return; @@ -217,6 +184,28 @@ public class MINVpnInnerConnection implements Runnable{ startHeartBeatThread(face,500); } + /** + * 现行版本下,由于外网服务器自己并不分配IP,tun的IP分配仍然由内网服务器做 + * @return + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public String getAllocatedAddressFromInnerServer(){ + // 1. 建立一条连接服务器的TCP-LogicFace + LogicFace face=initTCPLogicFace(this.mServerName,this.mServerPort); + if(face==null){ + LoggerHelper.severe("vpn connection run error: tcp logic face init error"); + return ""; + } + + // 2. VPN握手,交换信息。握手成功则返回内网服务器分配的IP + boolean flag=handShake(face,false); + if(flag){ + return this.allocatedAddress; + } + + return ""; + } + /** * 开启心跳包 * @param face @@ -446,14 +435,15 @@ public class MINVpnInnerConnection implements Runnable{ /** * VPN握手,交换信息。 * @param face + * @param needConfig 是否需要根据握手信息进行相应路由配置 */ @RequiresApi(api = Build.VERSION_CODES.Q) - private boolean handShake(LogicFace face){ + private boolean handShake(LogicFace face,boolean needConfig){ // 1. 构造兴趣包 // 构造兴趣包负载字段:加密密钥 & 本地前缀 ByteBuffer requestBuf = ByteBuffer.allocate(1024); byte len= ByteHelper.uint8ToByte(mEncryptedAESSeed.length); - if(!GlobalSetting.isSM4) { + if(!this.isSM4) { requestBuf.put((byte) 4).put(len).put(mEncryptedAESSeed).put(localFacePrefix.getBytes()).flip(); }else{ requestBuf.put((byte) 1).put(len).put(mEncryptedAESSeed).put(localFacePrefix.getBytes()).flip(); @@ -555,7 +545,9 @@ public class MINVpnInnerConnection implements Runnable{ // 4. 分析服务器的数据包应答,根据其中信息进行本地的路由配置 System.out.println("handshake result : " + this.handshakeResponse); - this.mParcelFileDescriptor = configure(this.handshakeResponse); + if(needConfig) { + this.mParcelFileDescriptor = configure(this.handshakeResponse); + } return true; } @@ -589,8 +581,9 @@ public class MINVpnInnerConnection implements Runnable{ break; case 'r': // 服务器配置 + // todo: 测试用 if(MINVpnSettingAPI.INSTANCE.getProxyMode() == 2) { - builder.addRoute(fields[1], Integer.parseInt(fields[2])); +// builder.addRoute(fields[1], Integer.parseInt(fields[2])); } break; case 'd': @@ -608,10 +601,6 @@ public class MINVpnInnerConnection implements Runnable{ if(MINVpnSettingAPI.INSTANCE.getProxyMode() == 0) { configRoute(builder); } - // 全局 - if(MINVpnSettingAPI.INSTANCE.getProxyMode() == 1) { - builder.addRoute("0.0.0.0",0); - } // 2. 创建一个接口 final ParcelFileDescriptor vpnInterface; diff --git a/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnService.java b/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnService.java index 909c5df..03c13ad 100644 --- a/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnService.java +++ b/app/src/main/java/com/pkusz/min_vpn_client/services/MINVpnService.java @@ -1,7 +1,7 @@ package com.pkusz.min_vpn_client.services; /* * @Author: Wang Feng - * @Description: + * @Description: 参考安卓项目组给出的ToyVPN * @Version: 1.0.0 * @Date: 10:52 2021/7/13 * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 @@ -58,16 +58,13 @@ public class MINVpnService extends VpnService implements Handler.Callback{ private final AtomicReference mConnectingThread = new AtomicReference<>(); private final AtomicReference mConnection = new AtomicReference<>(); - // vpn 连接 - private MINVpnInnerConnection minVpnConnection=null; + // vpn 连接: 内网或外网 + private MINVpnConnection minVpnConnection=null; // connection id private AtomicInteger mNextConnectionId = new AtomicInteger(1); // 通信intent private PendingIntent mConfigureIntent; - // vpn 外网连接 => 支持访问国外网站 -// private MINVpnExternalConnection minVpnExternalConnection=null; - @Override public void onCreate() { // 使用handler显示消息 @@ -145,7 +142,6 @@ public class MINVpnService extends VpnService implements Handler.Callback{ final String server = MINVpnSettingAPI.INSTANCE.getMirIpAddress(); final int port = MINVpnSettingAPI.INSTANCE.getMirPort(); // 生成消息加密密钥 -// final byte[] secret = RSACryptoForConnection.getRandomAESSeed().PlainSeed.getBytes(); final byte[] secret = SM4HelperForConnection.getRandomSeed(); // 获取随机16位长度密钥,用于对称加密 // 权限控制集合(并不知道为啥弄这个,我只是继承了由多人零零散散写、改了一年的客户端代码。) final boolean allow = false; @@ -155,20 +151,43 @@ public class MINVpnService extends VpnService implements Handler.Callback{ final String proxyHost = ""; final int proxyPort = 0; - // 默认开启内网服务器的连接 - minVpnConnection = new MINVpnInnerConnection( - this, mNextConnectionId.getAndIncrement(), server, port, secret, - proxyHost, proxyPort, allow, packages, GlobalSetting.isSM4); - // 对vpn connection进行各种监听设置,并启动执行其run函数 - startConnection(minVpnConnection); - - // 根据全局变量,判断 是否开启外网服务器的连接 - if(GlobalSetting.openFirewall){ -// final byte[] secretToExtra = SM4HelperForConnection.getRandomSeed(); // 获取随机16位长度密钥,用于对称加密 -// minVpnExternalConnection = new MINVpnExternalConnection( -// this,mNextConnectionId.getAndIncrement(), -// MINVpnSettingAPI.INSTANCE.getOneExtranetVpnServerInfo(),secretToExtra, -// proxyHost,proxyPort,allow,packages,GlobalSetting.isSM4); + // 根据mode,判断是否开启外网服务器的连接 + // 2表示内网,1表示外网 + System.out.println("当前VPN模式: "+MINVpnSettingAPI.INSTANCE.getProxyMode()); + if(MINVpnSettingAPI.INSTANCE.getProxyMode()==2){ + // 开启内网服务器的连接 + minVpnConnection = new MINVpnInnerConnection( + this, mNextConnectionId.getAndIncrement(), server, port, secret, + proxyHost, proxyPort, allow, packages, GlobalSetting.isSM4); + // 对vpn connection进行各种监听设置,并启动执行其run函数 + startConnection(minVpnConnection); + }else if(MINVpnSettingAPI.INSTANCE.getProxyMode()==1){ + // 通过与内网服务器握手,获取tun网卡的IP地址 + MINVpnInnerConnection innerConnection = new MINVpnInnerConnection( + this, mNextConnectionId.getAndIncrement(), server, port, secret, + proxyHost, proxyPort, allow, packages, GlobalSetting.isSM4); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + innerConnection.run(); + } + String allocateIP; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { + allocateIP=innerConnection.getAllocatedAddressFromInnerServer(); + System.out.println("已经为外网访问获取到IP: "+allocateIP); + }else { + System.out.println("外网访问获取到IP失败"); + return; + } + if(allocateIP.length()<=0){ + System.out.println("外网访问获取到IP失败"); + return; + } + // 开启外网服务器的连接 + minVpnConnection = new MINVpnExternalConnection( + this,mNextConnectionId.getAndIncrement(), + MINVpnSettingAPI.INSTANCE.getOneExtranetVpnServerInfo(),secret, + proxyHost,proxyPort,allow,packages,GlobalSetting.isSM4InExternal,allocateIP); + // 对vpn connection进行各种监听设置,并启动执行其run函数 + startConnection(minVpnConnection); } } @@ -176,7 +195,7 @@ public class MINVpnService extends VpnService implements Handler.Callback{ * 启动一条vpn连接 * @param connection */ - private void startConnection(final MINVpnInnerConnection connection) { + private void startConnection(final MINVpnConnection connection) { System.out.println("begin start connection..."); // 将vpn连接放入一个线程里 final Thread thread = new Thread(connection, "MINVpnThread"); @@ -210,6 +229,7 @@ public class MINVpnService extends VpnService implements Handler.Callback{ * 关掉vpn连接 */ private void disconnect(){ + System.out.println("收到了关掉连接的消息..."); mHandler.sendEmptyMessage(R.string.disconnected); setConnectingThread(null); setConnection(null); diff --git a/docs/MIN-VPN安卓客户端使用手册-20210806-王锋.docx b/docs/MIN-VPN安卓客户端使用手册-20210806-王锋.docx index 9c9c18e..270c285 100644 Binary files a/docs/MIN-VPN安卓客户端使用手册-20210806-王锋.docx and b/docs/MIN-VPN安卓客户端使用手册-20210806-王锋.docx differ