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 index 575c206..b629c0c 100644 --- 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 @@ -19,10 +19,17 @@ import android.util.Log; import androidx.annotation.RequiresApi; import com.pkusz.min_vpn_client.model.MINVpnAPI; +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.RSACryptoForConnection; +import com.pkusz.min_vpn_client.utils.RuleUtil; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Objects; import java.util.Set; import common.LoggerHelper; @@ -31,8 +38,10 @@ import component.Identifier; import component.IdentifierComponent; import logicface.LogicFace; import logicface.LogicFaceException; +import mgmt.RegisterPrefixHelper; import packet.Data; import packet.Interest; +import security.KeyChain; import static java.nio.charset.StandardCharsets.US_ASCII; @@ -145,16 +154,108 @@ public class MINVpnConnection implements Runnable{ @RequiresApi(api = Build.VERSION_CODES.Q) @Override public void run() { - // 建立一条连接服务器的TCP-LogicFace + // 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; } - // VPN握手,交换信息 + // 2. VPN握手,交换信息 handShake(face); + if(this.mParcelFileDescriptor==null){ + LoggerHelper.severe("vpn connection run error: handle shake error"); + } + // 3. 进行数据中转操作 + transferData(face); + } + + /** + * 双线程中转数据 + * @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; + 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); + + //buff2.put(IPPackageUtil.getSrcIpAddress(buff1.array())); + buff2.put(IPPackageUtil.StringAddress2byteAddress(allocatedAddress)); + byte[] ipPackageData = new byte[readLen]; + System.arraycopy(buff1.array(), 0, ipPackageData, 0, readLen); + byte[] encrytedIPPackage = AESHelperForConnection.encryptWithAES(mSharedSecretString, ipPackageData); + System.out.println("encryptedIPPackage len: " + encrytedIPPackage.length); + System.out.println("buff2 position: " + buff2.position()); + buff2.put(encrytedIPPackage); + encryptedLen = buff2.position(); + buff2.position(0); + buff2.limit(encryptedLen); + //buff2.flip(); + sendInterestPkt(face, buff2); + } + + } + }); + recvFromTunAndSendThread.start(); + + try { + RegisterPrefixHelper helper=new RegisterPrefixHelper(); + face.registerIdentifier(new Identifier(this.localPrefix),5000,helper); + + Interest interest= face.receiveInterest(5000); + + System.out.println("recv interest packet, name : " + interest.getName().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()); + byte[] decrpytedIPPackage = new byte[buf2.position()]; + System.arraycopy(buf2.array(), 0, decrpytedIPPackage, 0, buf2.position()); + ByteBuffer buf3 = ByteBuffer.allocate(5000); + buf3.put(Objects.requireNonNull(AESHelperForConnection.decryptWithAES(mSharedSecretString, decrpytedIPPackage))); + 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(); + } + } + + } catch (SecurityException | ComponentException | LogicFaceException e) { + e.printStackTrace(); + } + return true; } /** @@ -318,10 +419,25 @@ public class MINVpnConnection implements Runnable{ return vpnInterface; } + // 输出提示信息时使用 private final String getTag() { return MINVpnConnection.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 diff --git a/app/src/main/java/com/pkusz/min_vpn_client/utils/AESHelperForConnection.java b/app/src/main/java/com/pkusz/min_vpn_client/utils/AESHelperForConnection.java new file mode 100644 index 0000000..95d294e --- /dev/null +++ b/app/src/main/java/com/pkusz/min_vpn_client/utils/AESHelperForConnection.java @@ -0,0 +1,123 @@ +package com.pkusz.min_vpn_client.utils; + + +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import java.security.MessageDigest; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class AESHelperForConnection { + // AES + Base64 encrypt + @RequiresApi(api = Build.VERSION_CODES.O) + public static String encryptWithAES(String key, String message) { + try { + // Use md5 value as the real key + byte[] b = key.getBytes(); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] keyData = md.digest(b); + + SecretKeySpec skey = new SecretKeySpec(keyData, "AES"); + // Create an 8-byte initialization vector + byte[] iv = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f }; + AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv); + + Cipher ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + // CBC requires an initialization vector + ecipher.init(Cipher.ENCRYPT_MODE, skey, paramSpec); + + byte[] plaintext = message.getBytes(); + byte[] result = ecipher.doFinal(plaintext, 0, plaintext.length); + Base64.Encoder encoder = Base64.getEncoder(); +// return Base64.encodeToString(result, Base64.DEFAULT); + return encoder.encodeToString(result); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + // AES without base64 encrypt + public static byte[] encryptWithAES(String key, byte[] plaintext) { + try { + // Use md5 value as the real key + byte[] b = key.getBytes(); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] keyData = md.digest(b); + + SecretKeySpec skey = new SecretKeySpec(keyData, "AES"); + // Create an 8-byte initialization vector + byte[] iv = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f }; + AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv); + + Cipher ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + // CBC requires an initialization vector + ecipher.init(Cipher.ENCRYPT_MODE, skey, paramSpec); + return ecipher.doFinal(plaintext, 0, plaintext.length); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + // base64 -> AES decrypt + @RequiresApi(api = Build.VERSION_CODES.O) + public static String decryptWithAES(String key, String message) { + try { + // Use md5 value as the real key + byte[] b = key.getBytes(); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] keyData = md.digest(b); + + SecretKeySpec skey = new SecretKeySpec(keyData, "AES"); + // Create an 8-byte initialization vector + byte[] iv = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f }; + AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv); + + Cipher dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + // CBC requires an initialization vector + dcipher.init(Cipher.DECRYPT_MODE, skey, paramSpec); + Base64.Decoder decoder = Base64.getDecoder(); + byte[] messageData = decoder.decode(message); + byte[] result = dcipher.doFinal(messageData, 0, messageData.length); + // System.out.println("result = " + Arrays.toString(result)); + return new String(result); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + // AES without base64 + public static byte[] decryptWithAES(String key, final byte[] message) { + try { + // Use md5 value as the real key + byte[] b = key.getBytes(); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] keyData = md.digest(b); + + SecretKeySpec skey = new SecretKeySpec(keyData, "AES"); + // Create an 8-byte initialization vector + byte[] iv = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f }; + AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv); + + Cipher dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + // CBC requires an initialization vector + dcipher.init(Cipher.DECRYPT_MODE, skey, paramSpec); + return dcipher.doFinal(message, 0, message.length); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/app/src/main/java/com/pkusz/min_vpn_client/utils/IPPackageUtil.java b/app/src/main/java/com/pkusz/min_vpn_client/utils/IPPackageUtil.java new file mode 100644 index 0000000..a866bee --- /dev/null +++ b/app/src/main/java/com/pkusz/min_vpn_client/utils/IPPackageUtil.java @@ -0,0 +1,25 @@ +package com.pkusz.min_vpn_client.utils; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; + +public class IPPackageUtil { + + public static byte[] getSrcIpAddress(byte[] ipPackage) { + ByteBuffer byteBuffer = ByteBuffer.allocate(4); + byteBuffer.put(ipPackage, 12, 4); + byteBuffer.flip(); + return byteBuffer.array(); + } + + public static byte[] StringAddress2byteAddress(String address) { + try { + return InetAddress.getByName(address).getAddress(); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/app/src/main/java/com/pkusz/min_vpn_client/utils/RuleUtil.java b/app/src/main/java/com/pkusz/min_vpn_client/utils/RuleUtil.java new file mode 100644 index 0000000..72eafd2 --- /dev/null +++ b/app/src/main/java/com/pkusz/min_vpn_client/utils/RuleUtil.java @@ -0,0 +1,29 @@ +package com.pkusz.min_vpn_client.utils; + +import android.content.Context; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Scanner; + + +public class RuleUtil { + public static String[] getRules(Context context) { + ArrayList ruleArrayList = new ArrayList<>(); + try { + InputStream ruleStream = context.getAssets().open("rulelist.txt"); + Scanner scanner = new Scanner(ruleStream, "UTF-8"); + while (scanner.hasNextLine()) { + String rule = scanner.nextLine(); + if(rule.length() > 0) { + ruleArrayList.add(rule); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + String[] rules = (String[]) ruleArrayList.toArray(new String[ruleArrayList.size()]); + return rules; + } +}