├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── tunnel_icon_background.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── imgs │ │ │ │ └── black_tunnel.png │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── tunnel_icon.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ ├── tunnel_icon_round.png │ │ │ │ └── tunnel_icon_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── tunnel_icon.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ ├── tunnel_icon_round.png │ │ │ │ └── tunnel_icon_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── tunnel_icon.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ ├── tunnel_icon_round.png │ │ │ │ └── tunnel_icon_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── tunnel_icon.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ ├── tunnel_icon_round.png │ │ │ │ └── tunnel_icon_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── tunnel_icon.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ ├── tunnel_icon_round.png │ │ │ │ └── tunnel_icon_foreground.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── tunnel_icon.xml │ │ │ │ ├── ic_launcher.xml │ │ │ │ ├── tunnel_icon_round.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ │ └── ic_launcher_background.xml │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ ├── tunnel_icon-playstore.png │ │ ├── java │ │ │ └── com │ │ │ │ └── gf │ │ │ │ └── arrow4over6 │ │ │ │ ├── Constants.java │ │ │ │ ├── ArLog.java │ │ │ │ ├── ArrowVpnService.java │ │ │ │ ├── ArFrontFramework.java │ │ │ │ ├── PipeFrontEnd.java │ │ │ │ └── MainActivity.java │ │ ├── cpp │ │ │ ├── SocketPlugin.h │ │ │ ├── Singleton.h │ │ │ ├── arrowutils.cpp │ │ │ ├── arrowutils.h │ │ │ ├── ArBackFramework.h │ │ │ ├── arrowlib.cpp │ │ │ ├── CMakeLists.txt │ │ │ ├── PipeBackend.h │ │ │ ├── SocketPlugin.cpp │ │ │ ├── PipeBackend.cpp │ │ │ └── ArBackFramework.cpp │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── gf │ │ │ └── arrow4over6 │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── gf │ │ └── arrow4over6 │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── doc ├── imgs │ ├── app1.png │ ├── app2.png │ ├── fig1.png │ ├── info.png │ ├── speed1.png │ ├── speed2.png │ ├── frontend_module1.png │ └── Package arrow4over6.png └── 2017010636-瞿凡.pdf ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── misc.xml ├── runConfigurations.xml └── codeStyles │ └── Project.xml ├── gradle.properties ├── LICENSE ├── .gitignore ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='Arrow4Over6' 2 | include ':app' 3 | -------------------------------------------------------------------------------- /doc/imgs/app1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/doc/imgs/app1.png -------------------------------------------------------------------------------- /doc/imgs/app2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/doc/imgs/app2.png -------------------------------------------------------------------------------- /doc/imgs/fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/doc/imgs/fig1.png -------------------------------------------------------------------------------- /doc/imgs/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/doc/imgs/info.png -------------------------------------------------------------------------------- /doc/imgs/speed1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/doc/imgs/speed1.png -------------------------------------------------------------------------------- /doc/imgs/speed2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/doc/imgs/speed2.png -------------------------------------------------------------------------------- /doc/2017010636-瞿凡.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/doc/2017010636-瞿凡.pdf -------------------------------------------------------------------------------- /doc/imgs/frontend_module1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/doc/imgs/frontend_module1.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Arrow4Over6 3 | 4 | -------------------------------------------------------------------------------- /doc/imgs/Package arrow4over6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/doc/imgs/Package arrow4over6.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/imgs/black_tunnel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/imgs/black_tunnel.png -------------------------------------------------------------------------------- /app/src/main/tunnel_icon-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/tunnel_icon-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/tunnel_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-hdpi/tunnel_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/tunnel_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-mdpi/tunnel_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/tunnel_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xhdpi/tunnel_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/tunnel_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xxhdpi/tunnel_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/tunnel_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xxxhdpi/tunnel_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/tunnel_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-hdpi/tunnel_icon_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/tunnel_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-mdpi/tunnel_icon_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/tunnel_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xhdpi/tunnel_icon_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/tunnel_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xxhdpi/tunnel_icon_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/tunnel_icon_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-hdpi/tunnel_icon_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/tunnel_icon_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-mdpi/tunnel_icon_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/tunnel_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xxxhdpi/tunnel_icon_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/tunnel_icon_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xhdpi/tunnel_icon_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/tunnel_icon_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xxhdpi/tunnel_icon_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/tunnel_icon_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renzibei/Arrow4Over6/master/app/src/main/res/mipmap-xxxhdpi/tunnel_icon_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values/tunnel_icon_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #A5FFFD 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu May 14 18:19:35 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/tunnel_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/tunnel_icon_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/gf/arrow4over6/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.gf.arrow4over6; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/gf/arrow4over6/Constants.java: -------------------------------------------------------------------------------- 1 | package com.gf.arrow4over6; 2 | 3 | abstract class Constants { 4 | static final String DEBUG_TAG = "AW_DEBUG"; 5 | static final String ERROR_TAG = "AW_ERROR"; 6 | static final String INFO_TAG = "AW_INFO"; 7 | static final String VERBOSE_TAG = "AW_VERBOSE"; 8 | static final String WARN_TAG = "AW_WARN"; 9 | 10 | static final String INIT_IPV6_ADDRESS = "2402:f000:4:72:808::9a47"; 11 | static final String INIT_PORT = "5678"; 12 | 13 | static final int VPN_REQUEST_CODE = 0x1007; 14 | } 15 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/cpp/SocketPlugin.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Fan Qu on 5/17/20. 3 | // 4 | 5 | #ifndef ARROW4OVER6_SOCKETPLUGIN_H 6 | #define ARROW4OVER6_SOCKETPLUGIN_H 7 | 8 | 9 | #include "Singleton.h" 10 | 11 | class SocketPlugin: public Singleton { 12 | private: 13 | friend Singleton; 14 | int m_sockfd; 15 | 16 | 17 | 18 | SocketPlugin() {}; 19 | 20 | 21 | public: 22 | 23 | // now default interpret addr as ipv6 addr 24 | int connectSocket(const char* addrStr, int port); 25 | int getSockFd(); 26 | int closeSocket(); 27 | 28 | int writeMsg(const void* msg, size_t msgLen); 29 | int readMsg(void* buf, size_t msgLen); 30 | ~SocketPlugin() {}; 31 | 32 | }; 33 | 34 | 35 | #endif //ARROW4OVER6_SOCKETPLUGIN_H 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/gf/arrow4over6/ArLog.java: -------------------------------------------------------------------------------- 1 | package com.gf.arrow4over6; 2 | 3 | import android.util.Log; 4 | 5 | public class ArLog { 6 | public static int d(String s) { 7 | return Log.d(Constants.DEBUG_TAG, s); 8 | } 9 | 10 | public static int e(String s) { 11 | return Log.e(Constants.ERROR_TAG, s); 12 | } 13 | 14 | public static int e(String s, Throwable t) { 15 | return Log.e(Constants.ERROR_TAG, s, t); 16 | } 17 | 18 | public static int v(String s) { 19 | return Log.v(Constants.VERBOSE_TAG, s); 20 | } 21 | 22 | public static int i(String s) { 23 | return Log.i(Constants.INFO_TAG, s); 24 | } 25 | 26 | public static int w(String s) { 27 | return Log.w(Constants.WARN_TAG, s); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/cpp/Singleton.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Fan Qu on 5/16/20. 3 | // 4 | 5 | #ifndef ARROW4OVER6_SINGLETON_H 6 | #define ARROW4OVER6_SINGLETON_H 7 | 8 | #include 9 | 10 | template 11 | class Singleton { 12 | protected: 13 | static T* _instance; 14 | static std::mutex singleMutex; 15 | 16 | virtual ~Singleton() {} 17 | 18 | public: 19 | static T* getInstance() { 20 | if(_instance == nullptr) { 21 | std::lock_guard lockGuard(singleMutex); 22 | if(_instance == nullptr) 23 | _instance = new T(); 24 | } 25 | return _instance; 26 | } 27 | }; 28 | 29 | template 30 | T* Singleton::_instance = nullptr; 31 | 32 | template 33 | std::mutex Singleton::singleMutex; 34 | 35 | 36 | #endif //ARROW4OVER6_SINGLETON_H 37 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/gf/arrow4over6/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.gf.arrow4over6; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.gf.arrow4over6", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/cpp/arrowutils.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Fan Qu on 5/14/20. 3 | // 4 | #include "arrowutils.h" 5 | #include 6 | #include 7 | #include 8 | 9 | ssize_t readnBytes(int fd, void *buf, size_t nbyte) { 10 | size_t hasRead = 0; 11 | char* dest = (char*)buf; 12 | while(hasRead < nbyte) { 13 | ssize_t tempLen = read(fd, dest + hasRead, nbyte - hasRead); 14 | if(tempLen < 0) { 15 | LOGE("Error happens when read fd , %s", strerror(errno)); 16 | return -1; 17 | } 18 | hasRead += tempLen; 19 | } 20 | return 0; 21 | } 22 | 23 | ssize_t writenBytes(int fd, const void *buf, size_t nbyte) { 24 | size_t hasWritten = 0; 25 | const char* src = (const char*)buf; 26 | while(hasWritten < nbyte) { 27 | ssize_t tempLen = write(fd, src + hasWritten, nbyte - hasWritten); 28 | if(tempLen < 0) { 29 | LOGE("Errors when write fd, %s", strerror(errno)); 30 | return -1; 31 | } 32 | hasWritten += tempLen; 33 | } 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/cpp/arrowutils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Fan Qu on 5/14/20. 3 | // 4 | 5 | #ifndef ARROW4OVER6_ARROWUTILS_H 6 | #define ARROW4OVER6_ARROWUTILS_H 7 | 8 | #include 9 | #include 10 | 11 | #define D_TAG "AW_CXX_DEBUG" 12 | #define I_TAG "AW_CXX_INFO" 13 | #define W_TAG "AW_CXX_WARN" 14 | #define E_TAG "AW_CXX_ERROR" 15 | #define V_TAG "AW_CXX_VERBOSE" 16 | 17 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, D_TAG ,__VA_ARGS__) 18 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, I_TAG ,__VA_ARGS__) 19 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, W_TAG ,__VA_ARGS__) 20 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, E_TAG ,__VA_ARGS__) 21 | #define LOGV(...) __android_log_print(ANDROID_LOG_FATAL,V_TAG ,__VA_ARGS__) 22 | 23 | #define MSG_DATA_LEN 4096 24 | 25 | struct Msg{ 26 | int length; 27 | char type; 28 | char data[MSG_DATA_LEN]; 29 | }; 30 | 31 | ssize_t readnBytes(int fd, void *buf, size_t nbyte); 32 | ssize_t writenBytes(int fd, const void *buf, size_t nbyte); 33 | 34 | #endif //ARROW4OVER6_ARROWUTILS_H 35 | -------------------------------------------------------------------------------- /app/src/main/cpp/ArBackFramework.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Fan Qu on 5/17/20. 3 | // 4 | 5 | #ifndef ARROW4OVER6_ARBACKFRAMEWORK_H 6 | #define ARROW4OVER6_ARBACKFRAMEWORK_H 7 | 8 | #include "Singleton.h" 9 | #include "arrowutils.h" 10 | 11 | class ArBackFramework: public Singleton { 12 | private: 13 | friend Singleton; 14 | 15 | std::atomic stopFlag; 16 | std::atomic connectSeconds, lastKeepAliveTime; 17 | std::atomic sendPackets, receivePackets; 18 | std::atomic sendBytes, receiveBytes; 19 | 20 | 21 | 22 | int vpnFd; 23 | 24 | ArBackFramework(); 25 | 26 | void connectSocket(); 27 | 28 | void vpnProcess(); 29 | 30 | void initStatistics(); 31 | 32 | void stopRun(); 33 | 34 | int receiveMessage(Msg &msg); 35 | int sendMessage(const Msg& msg); 36 | 37 | ~ArBackFramework() {}; 38 | 39 | public: 40 | void run(const char* appDirPath); 41 | void readTunnel(); 42 | void writeTunnel(); 43 | void timerProcess(); 44 | bool shouldStop(); 45 | }; 46 | 47 | 48 | #endif //ARROW4OVER6_ARBACKFRAMEWORK_H 49 | -------------------------------------------------------------------------------- /app/src/main/cpp/arrowlib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "arrowutils.h" 5 | #include "PipeBackend.h" 6 | #include "ArBackFramework.h" 7 | 8 | void testPipe() { 9 | PipeBackend::getInstance()->writeInt(101); 10 | int recvInt = 0; 11 | int readRet = PipeBackend::getInstance()->readInt(recvInt); 12 | if(readRet == 0) { 13 | LOGI("backend Receive int %d", recvInt); 14 | } 15 | else { 16 | LOGE("backend fail to receive int"); 17 | } 18 | } 19 | 20 | void runLoop(std::string appDirPath) { 21 | ArBackFramework::getInstance()->run(appDirPath.c_str()); 22 | } 23 | 24 | extern "C" JNIEXPORT jint JNICALL 25 | Java_com_gf_arrow4over6_MainActivity_startBackend( 26 | JNIEnv* env, 27 | jobject /* this */, jstring appDir) { 28 | 29 | LOGI("Receive?"); 30 | std::string hello = "Hello from C++"; 31 | std::string appDirStr = env->GetStringUTFChars(appDir, NULL); 32 | std::thread runThread(runLoop, appDirStr); 33 | runThread.detach(); 34 | 35 | // std::thread testThread(testPipe); 36 | // testThread.detach(); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.gf.arrow4over6" 9 | minSdkVersion 23 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | 16 | externalNativeBuild { 17 | cmake { 18 | cppFlags "-std=c++11" 19 | } 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | 30 | externalNativeBuild { 31 | cmake { 32 | path "src/main/cpp/CMakeLists.txt" 33 | version "3.10.2" 34 | } 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation fileTree(dir: 'libs', include: ['*.jar']) 40 | 41 | implementation 'androidx.appcompat:appcompat:1.1.0' 42 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 43 | testImplementation 'junit:junit:4.12' 44 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 45 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Ren Zibei 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | 16 | *.apk 17 | *.aar 18 | *.ap_ 19 | *.aab 20 | # Files for the ART/Dalvik VM 21 | *.dex 22 | # Java class files 23 | *.class 24 | # Generated files 25 | bin/ 26 | gen/ 27 | out/ 28 | # Uncomment the following line in case you need and you don't have the release build type files in your app 29 | # release/ 30 | # Gradle files 31 | .gradle/ 32 | build/ 33 | # Local configuration file (sdk path, etc) 34 | local.properties 35 | # Proguard folder generated by Eclipse 36 | proguard/ 37 | # Log Files 38 | *.log 39 | # Android Studio Navigation editor temp files 40 | .navigation/ 41 | # Android Studio captures folder 42 | captures/ 43 | # IntelliJ 44 | *.iml 45 | .idea/workspace.xml 46 | .idea/tasks.xml 47 | .idea/gradle.xml 48 | .idea/assetWizardSettings.xml 49 | .idea/dictionaries 50 | .idea/libraries 51 | # Android Studio 3 in .gitignore file. 52 | .idea/caches 53 | .idea/modules.xml 54 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 55 | .idea/navEditor.xml 56 | # Keystore files 57 | # Uncomment the following lines if you do not want to check your keystore files in. 58 | #*.jks 59 | #*.keystore 60 | # External native build folder generated in Android Studio 2.2 and later 61 | .externalNativeBuild 62 | .cxx/ 63 | # Google Services (e.g. APIs or Firebase) 64 | # google-services.json 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | # fastlane 70 | fastlane/report.xml 71 | fastlane/Preview.html 72 | fastlane/screenshots 73 | fastlane/test_output 74 | fastlane/readme.md 75 | # Version control 76 | vcs.xml 77 | # lint 78 | lint/intermediates/ 79 | lint/generated/ 80 | lint/outputs/ 81 | lint/tmp/ 82 | -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | 6 | cmake_minimum_required(VERSION 3.4.1) 7 | 8 | # Creates and names a library, sets it as either STATIC 9 | # or SHARED, and provides the relative paths to its source code. 10 | # You can define multiple libraries, and CMake builds them for you. 11 | # Gradle automatically packages shared libraries with your APK. 12 | 13 | add_library( # Sets the name of the library. 14 | arrowlib 15 | 16 | # Sets the library as a shared library. 17 | SHARED 18 | 19 | # Provides a relative path to your source file(s). 20 | arrowlib.cpp arrowutils.cpp PipeBackend.cpp ArBackFramework.cpp SocketPlugin.cpp) 21 | 22 | # Searches for a specified prebuilt library and stores the path as a 23 | # variable. Because CMake includes system libraries in the search path by 24 | # default, you only need to specify the name of the public NDK library 25 | # you want to add. CMake verifies that the library exists before 26 | # completing its build. 27 | 28 | find_library( # Sets the name of the path variable. 29 | log-lib 30 | 31 | # Specifies the name of the NDK library that 32 | # you want CMake to locate. 33 | log ) 34 | 35 | # Specifies libraries CMake should link to your target library. You 36 | # can link multiple libraries, such as libraries you define in this 37 | # build script, prebuilt third-party libraries, or system libraries. 38 | 39 | target_link_libraries( # Specifies the target library. 40 | arrowlib 41 | 42 | # Links the target library to the log library 43 | # included in the NDK. 44 | ${log-lib} ) -------------------------------------------------------------------------------- /app/src/main/java/com/gf/arrow4over6/ArrowVpnService.java: -------------------------------------------------------------------------------- 1 | package com.gf.arrow4over6; 2 | 3 | import android.content.Intent; 4 | import android.net.VpnService; 5 | import android.os.ParcelFileDescriptor; 6 | 7 | public class ArrowVpnService extends VpnService { 8 | 9 | @Override 10 | public int onStartCommand(Intent intent, int flags, int startId) { 11 | ArLog.i("begin ArrowVpnService"); 12 | String[] ipInfos = intent.getStringArrayExtra("ipInfos"); 13 | assert ipInfos != null; 14 | int vpnFd = setVpn(ipInfos); 15 | sendVpnFd(vpnFd); 16 | return super.onStartCommand(intent, flags, startId); 17 | 18 | } 19 | 20 | private void sendVpnFd(final int vpnFd) { 21 | new Thread(new Runnable() { 22 | @Override 23 | public void run() { 24 | PipeFrontEnd.getInstance().writeInt(102); 25 | PipeFrontEnd.getInstance().writeInt(vpnFd); 26 | } 27 | }).start(); 28 | } 29 | 30 | private int setVpn(String[] ipInfos) { 31 | int sockFd = 0; 32 | try { 33 | sockFd = Integer.parseInt(ipInfos[5]); 34 | } catch (NumberFormatException e) { 35 | ArLog.e("sockfd can not be interpreted as int" + ipInfos[5]); 36 | ArLog.e(e.getMessage(), e); 37 | return -1; 38 | } 39 | 40 | this.protect(sockFd); 41 | 42 | Builder builder = new Builder(); 43 | builder.setSession(getString(R.string.app_name)); 44 | builder.addAddress(ipInfos[0], 32); 45 | builder.addDnsServer(ipInfos[2]); 46 | builder.addDnsServer(ipInfos[3]); 47 | builder.addDnsServer(ipInfos[4]); 48 | builder.setMtu(1500); 49 | builder.addRoute("0.0.0.0", 0); 50 | 51 | ParcelFileDescriptor vpnFileDescriptor = builder.establish(); 52 | assert vpnFileDescriptor != null; 53 | int vpnFd = vpnFileDescriptor.getFd(); 54 | ArLog.i("vpn fd = " + vpnFd); 55 | return vpnFd; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/cpp/PipeBackend.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Fan Qu on 5/14/20. 3 | // 4 | 5 | #ifndef ARROW4OVER6_PIPEBACKEND_H 6 | #define ARROW4OVER6_PIPEBACKEND_H 7 | 8 | #include 9 | #include "Singleton.h" 10 | 11 | //#define AW_READ_BUFFER_SIZE 4096 12 | //#define AW_WRITE_BUFFER_SIZE 4096 13 | 14 | class PipeBackend: public Singleton { 15 | 16 | private: 17 | 18 | friend Singleton; 19 | // static PipeBackend* _instance; 20 | // static std::mutex singleMutex, readMutex, writeMutex; 21 | static std::mutex readMutex, writeMutex; 22 | static char* pipeDirPath; 23 | 24 | char* toPipeFrontPath; 25 | char* fromPipeFrontPath; 26 | 27 | static constexpr size_t WRITE_BUFFER_SIZE = 1 << 14; 28 | static constexpr size_t READ_BUFFER_SIZE = 1 << 14; 29 | 30 | char readBuffer[READ_BUFFER_SIZE]; 31 | char writeBuffer[WRITE_BUFFER_SIZE]; 32 | 33 | int toFrontEndFd, fromFrontEndFd; 34 | 35 | PipeBackend(); 36 | 37 | void initPipeName(const char *dirPath); 38 | 39 | int makePipe(); 40 | 41 | int openPipe(); 42 | 43 | // read len at the beginning, return 0 if succeed 44 | int readLength(int &len); 45 | 46 | 47 | 48 | int writeLength(int len); 49 | 50 | 51 | 52 | ~PipeBackend(); 53 | 54 | public: 55 | 56 | // use setPipeDirPath first 57 | // static PipeBackend* getInstance() { 58 | // if(_instance == nullptr) { 59 | // std::lock_guard lockGuard(singleMutex); 60 | // if(_instance == nullptr) 61 | // _instance = new PipeBackend(); 62 | // } 63 | // return _instance; 64 | // } 65 | 66 | //before getInstance, should set PipeDirPath 67 | static void setPipeDirPath(const char* dirPath); 68 | 69 | //read n bytes to readBuffer, return n 70 | int readBytes(); 71 | 72 | int readBytes(char* buf, int bufLen); 73 | 74 | //read Int to x, return 0 if succeed 75 | int readInt(int &x); 76 | 77 | //write len bytes from writeBuffer, return 0 if succeed 78 | int writeBytes(const char* msg, int len); 79 | 80 | int writeInt(int dataInt); 81 | }; 82 | 83 | 84 | #endif //ARROW4OVER6_PIPEBACKEND_H 85 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/cpp/SocketPlugin.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Fan Qu on 5/17/20. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "SocketPlugin.h" 10 | #include "arrowutils.h" 11 | #include "PipeBackend.h" 12 | 13 | int SocketPlugin::connectSocket(const char *addrStr, int port) { 14 | 15 | int socketFd = 0; 16 | if((socketFd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) { 17 | LOGE("Fail to create socket, %s", strerror(errno)); 18 | return -1; 19 | } 20 | struct sockaddr_in6 dest; 21 | memset(&dest, 0, sizeof(dest)); 22 | dest.sin6_family = AF_INET6; 23 | dest.sin6_port = htons(port); 24 | LOGI("server ipv6 address %s", addrStr); 25 | if(inet_pton(AF_INET6, addrStr, &dest.sin6_addr) != 1) { 26 | LOGE("Fail to convert ipv6 Address, %s", strerror(errno)); 27 | return -1; 28 | } 29 | LOGI("before connect socket"); 30 | if(connect(socketFd, (struct sockaddr*) &dest, sizeof(dest)) < 0) { 31 | LOGE("Fail to connect socket, %s", strerror(errno)); 32 | return -1; 33 | } 34 | LOGI("connect succeed"); 35 | this->m_sockfd = socketFd; 36 | return 0; 37 | } 38 | 39 | int SocketPlugin::closeSocket() { 40 | 41 | int ret = close(this->m_sockfd); 42 | if(ret == -1) { 43 | LOGE("close socket failed", strerror(errno)); 44 | } 45 | LOGI("close socket"); 46 | return ret; 47 | } 48 | 49 | int SocketPlugin::getSockFd() { 50 | return this->m_sockfd; 51 | } 52 | 53 | int SocketPlugin::writeMsg(const void *msg, size_t msgLen) { 54 | // size_t hasWritten = 0; 55 | // const char* dest = (const char*)msg; 56 | // while(hasWritten < msgLen) { 57 | // ssize_t tempLen = write(m_sockfd, dest + hasWritten, msgLen - hasWritten); 58 | // if(tempLen < 0) { 59 | // LOGE("Errors when write socket, %s", strerror(errno)); 60 | // return -1; 61 | // } 62 | // hasWritten += tempLen; 63 | // } 64 | // return 0; 65 | return writenBytes(this->m_sockfd, msg, msgLen); 66 | } 67 | 68 | int SocketPlugin::readMsg(void *buf, size_t msgLen) { 69 | // size_t hasRead = 0; 70 | // char* src = (char*)buf; 71 | // while(hasRead < msgLen) { 72 | // ssize_t tempLen = read(m_sockfd, src + hasRead, msgLen - hasRead); 73 | // if(tempLen < 0) { 74 | // LOGE("Error happens when read socket, %s", strerror(errno)); 75 | // return -1; 76 | // } 77 | // hasRead += tempLen; 78 | // } 79 | // return 0; 80 | return readnBytes(this->m_sockfd, buf, msgLen); 81 | } -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /app/src/main/java/com/gf/arrow4over6/ArFrontFramework.java: -------------------------------------------------------------------------------- 1 | package com.gf.arrow4over6; 2 | 3 | import android.content.Intent; 4 | import android.net.VpnService; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | 8 | import androidx.appcompat.app.AlertDialog; 9 | 10 | import java.util.Arrays; 11 | 12 | public enum ArFrontFramework { 13 | INSTANCE; 14 | 15 | public static ArFrontFramework getInstance() { 16 | return INSTANCE; 17 | } 18 | 19 | private void showConnectErrorAlert() { 20 | Handler mainHandler = new Handler(Looper.getMainLooper()); 21 | mainHandler.post(new Runnable() { 22 | @Override 23 | public void run() { 24 | new AlertDialog.Builder(MainActivity.getInstance()) 25 | .setTitle("连接错误") 26 | .setMessage("连接发生错误,请检查网络状态,IP及端口") 27 | .setPositiveButton("Ok", null) 28 | .show(); 29 | } 30 | }); 31 | } 32 | 33 | public void establishConnect(final String ipv6AddrStr, final int port) { 34 | new Thread(new Runnable() { 35 | @Override 36 | public void run() { 37 | ArLog.i("before establish Connect"); 38 | PipeFrontEnd.waitUntilInitialized(); 39 | //send connect signal, ipv6 address and port 40 | PipeFrontEnd.getInstance().writeInt(101); 41 | PipeFrontEnd.getInstance().writeBytes(ipv6AddrStr.getBytes()); 42 | PipeFrontEnd.getInstance().writeInt(port); 43 | 44 | // receive ip info and sockfd 45 | int code = PipeFrontEnd.getInstance().readInt(); 46 | // connect failed 47 | if(code == 204) { 48 | showConnectErrorAlert(); 49 | return; 50 | } 51 | if(code != 201) { 52 | ArLog.e("read code not equals 201"); 53 | return; 54 | } 55 | byte[] data = PipeFrontEnd.getInstance().readBytes(); 56 | int dataLen = data.length; 57 | String dataStr = new String(Arrays.copyOfRange(data, 0, dataLen)); 58 | 59 | int sockFd = PipeFrontEnd.getInstance().readInt(); 60 | ArLog.i("ip info " + dataStr); 61 | 62 | dataStr = dataStr + sockFd; 63 | String[] ipInfos = dataStr.split(" "); 64 | if(ipInfos.length != 6) { 65 | ArLog.e("Error, ip info len != 6, ip info len = " + ipInfos.length); 66 | for(int i = 0; i < ipInfos.length; ++i) { 67 | ArLog.e(ipInfos[i]); 68 | } 69 | return; 70 | } 71 | ArLog.i("sockfd : " + ipInfos[5]); 72 | MainActivity.updateIpInfo(ipInfos[0]); 73 | 74 | MainActivity.establishVpn(ipInfos); 75 | readLoop(); 76 | 77 | } 78 | }).start(); 79 | } 80 | 81 | private void readLoop() { 82 | int readCode = 0; 83 | ArLog.i("frontend readloop start"); 84 | while(true) { 85 | readCode = PipeFrontEnd.getInstance().readInt(); 86 | PipeFrontEnd frontEnd = PipeFrontEnd.getInstance(); 87 | if(readCode == 202) { 88 | int connectTime = frontEnd.readInt(); 89 | int uploadSpeed = frontEnd.readInt(); 90 | int downLoadSpeed = frontEnd.readInt(); 91 | int uploadPackets = frontEnd.readInt(); 92 | int downloadPackets = frontEnd.readInt(); 93 | int uploadBandwidth = frontEnd.readInt(); 94 | int downloadBandwidth = frontEnd.readInt(); 95 | MainActivity.updateStatistics(connectTime, uploadSpeed, downLoadSpeed, uploadPackets, downloadPackets, uploadBandwidth, downloadBandwidth); 96 | } 97 | else if(readCode == 203) { 98 | ArLog.i("end front end readLoop"); 99 | MainActivity.clearStatistics(); 100 | break; 101 | } 102 | else { 103 | ArLog.e("unknown readCode = " + readCode); 104 | } 105 | } 106 | } 107 | 108 | 109 | } 110 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/java/com/gf/arrow4over6/PipeFrontEnd.java: -------------------------------------------------------------------------------- 1 | package com.gf.arrow4over6; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.BufferedOutputStream; 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.FileNotFoundException; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.nio.ByteBuffer; 11 | import java.nio.ByteOrder; 12 | import java.util.concurrent.locks.ReentrantReadWriteLock; 13 | 14 | public enum PipeFrontEnd { 15 | // private volatile static PipeFrontEnd _instance; 16 | INSTANCE; 17 | 18 | private static String pipeDirPath; 19 | 20 | private static final String fromPipeFrontFileName = "FromPipeFront"; 21 | private static final String toPipeFrontFileName = "ToPipeFront"; 22 | 23 | private static volatile boolean isReady = false; 24 | 25 | private static final ReentrantReadWriteLock readyLock = new ReentrantReadWriteLock(); 26 | 27 | private String fromPipeFrontEndPath; 28 | private String toPipeFrontEndPath; 29 | 30 | private BufferedInputStream pipeInputStream; 31 | private BufferedOutputStream pipeOutputStream; 32 | 33 | private void setInstanceFlagReady() { 34 | readyLock.writeLock().lock(); 35 | isReady = true; 36 | readyLock.writeLock().unlock(); 37 | } 38 | 39 | PipeFrontEnd() { 40 | ArLog.i("PipeFrontEnd begins constructing"); 41 | 42 | } 43 | 44 | public static boolean isInstanceReady() { 45 | boolean ret = false; 46 | readyLock.readLock().lock(); 47 | ret = isReady; 48 | readyLock.readLock().unlock(); 49 | return ret; 50 | } 51 | 52 | public static PipeFrontEnd getInstance() { 53 | // if(_instance == null) { 54 | // synchronized (PipeFrontEnd.class) { 55 | // if(_instance == null) { 56 | // _instance = new PipeFrontEnd(); 57 | // } 58 | // } 59 | // } 60 | // return _instance; 61 | return INSTANCE; 62 | } 63 | 64 | // set pipeDirPath before getInstance 65 | public static void setPipeDirPathAndBuild(String dirPath) { 66 | ArLog.i("set pipe dir path to" + dirPath); 67 | pipeDirPath = dirPath; 68 | INSTANCE.constructInstance(); 69 | } 70 | 71 | public static void waitUntilInitialized() { 72 | boolean isPipeReady = false; 73 | while(!isPipeReady) { 74 | isPipeReady = PipeFrontEnd.isInstanceReady(); 75 | if(!isPipeReady) { 76 | try { 77 | ArLog.i("Pipe Frontend is not ready"); 78 | Thread.sleep(100); 79 | } 80 | catch (InterruptedException e) { 81 | ArLog.e(e.getMessage(), e); 82 | } 83 | } 84 | } 85 | } 86 | 87 | private void buildInstance() { 88 | ArLog.i("before build Instance"); 89 | initPipeFilePath(); 90 | makePipe(); 91 | setInstanceFlagReady(); 92 | } 93 | 94 | // Asynchronously construct the instance 95 | private void constructInstance() { 96 | new Thread(new Runnable() { 97 | @Override 98 | public void run() { 99 | ArLog.i("asyn construct Instance"); 100 | buildInstance(); 101 | } 102 | }).start(); 103 | 104 | } 105 | 106 | private int readBeginLength() { 107 | byte[] dataByte = new byte[4]; 108 | try { 109 | int realLen = pipeInputStream.read(dataByte, 0, 4); 110 | if(realLen < 4) { 111 | ArLog.e("read realLen < 4, realLen = " + realLen); 112 | } 113 | } catch(IOException e) { 114 | ArLog.e("Fail to read begin length"); 115 | ArLog.e(e.getMessage(), e); 116 | return 0; 117 | } 118 | ByteBuffer byteBuffer = ByteBuffer.wrap(dataByte); 119 | byteBuffer.order(ByteOrder.nativeOrder()); 120 | return byteBuffer.getInt(); 121 | } 122 | 123 | public int readInt() { 124 | int beginLen = readBeginLength(); 125 | if(beginLen != 4) { 126 | ArLog.e("read begin len for int not equals 4"); 127 | return -1; 128 | } 129 | return readBeginLength(); 130 | } 131 | 132 | public byte[] readBytes() { 133 | int dataLen = readBeginLength(); 134 | byte[] bytes = new byte[dataLen]; 135 | try { 136 | int realLen = pipeInputStream.read(bytes, 0, dataLen); 137 | if(realLen < dataLen) { 138 | ArLog.e("read realLen < targetLen , realLen = " + realLen + " targetLen = " +dataLen); 139 | } 140 | } catch(IOException e) { 141 | ArLog.e("Fail to readBytes"); 142 | ArLog.e(e.toString(), e); 143 | bytes = null; 144 | } 145 | return bytes; 146 | } 147 | 148 | private void writeBeginLength(int len) { 149 | ByteBuffer byteBuffer = ByteBuffer.allocate(4); 150 | byteBuffer.order(ByteOrder.nativeOrder()); 151 | byteBuffer.putInt(len); 152 | try { 153 | pipeOutputStream.write(byteBuffer.array()); 154 | pipeOutputStream.flush(); 155 | } catch (IOException e) { 156 | ArLog.e("Fail to write begin length"); 157 | ArLog.e(e.getMessage(), e); 158 | } 159 | } 160 | 161 | public void writeBytes(byte[] bytes) { 162 | int dataLen = bytes.length; 163 | writeBeginLength(dataLen); 164 | try { 165 | pipeOutputStream.write(bytes); 166 | pipeOutputStream.flush(); 167 | } catch (IOException e) { 168 | ArLog.e("Fail to write bytes through pipe"); 169 | ArLog.e(e.getMessage(), e); 170 | } 171 | } 172 | 173 | public void writeInt(int intData) { 174 | writeBeginLength(4); 175 | writeBeginLength(intData); 176 | } 177 | 178 | 179 | private void initPipeFilePath() { 180 | fromPipeFrontEndPath = pipeDirPath + "/" + fromPipeFrontFileName; 181 | toPipeFrontEndPath = pipeDirPath + "/" + toPipeFrontFileName; 182 | } 183 | 184 | private void waitForFileExists(File file) { 185 | while(!file.exists()) { 186 | try { 187 | Thread.sleep(100); 188 | ArLog.i("No pipe file, sleep 50ms"); 189 | } catch (InterruptedException e) { 190 | ArLog.e(e.getMessage(), e); 191 | } 192 | } 193 | } 194 | 195 | private void makePipe() { 196 | File inFile = new File(toPipeFrontEndPath); 197 | ArLog.i("wait for fifo file" + toPipeFrontEndPath); 198 | waitForFileExists(inFile); 199 | File outFile = new File(fromPipeFrontEndPath); 200 | ArLog.i("wait for fifo file" + fromPipeFrontEndPath); 201 | waitForFileExists(outFile); 202 | 203 | try { 204 | FileInputStream fileInputStream = new FileInputStream(inFile); 205 | BufferedInputStream inputStream = new BufferedInputStream(fileInputStream); 206 | FileOutputStream fileOutputStream = new FileOutputStream(outFile); 207 | BufferedOutputStream outputStream = new BufferedOutputStream(fileOutputStream); 208 | this.pipeInputStream = inputStream; 209 | this.pipeOutputStream = outputStream; 210 | } catch (FileNotFoundException e) { 211 | ArLog.e("Fail to open pipe File Stream"); 212 | ArLog.e(e.getMessage(), e); 213 | return; 214 | } 215 | ArLog.i("Frontend Finish open pipe"); 216 | 217 | } 218 | 219 | 220 | } 221 | -------------------------------------------------------------------------------- /app/src/main/cpp/PipeBackend.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Fan Qu on 5/14/20. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "PipeBackend.h" 10 | #include "arrowutils.h" 11 | 12 | #define TO_PIPE_FRONT_FILE_NAME "ToPipeFront" 13 | #define FROM_PIPE_FRONT_FILE_NAME "FromPipeFront" 14 | 15 | //PipeBackend* PipeBackend::_instance = nullptr; 16 | char* PipeBackend::pipeDirPath = nullptr; 17 | //const char* PipeBackend::toPipeFrontPath = "ToPipeFront"; 18 | //const char* PipeBackend::fromPipeFrontPath = "FromPipeFront"; 19 | std::mutex PipeBackend::readMutex, PipeBackend::writeMutex; 20 | 21 | PipeBackend::PipeBackend(): 22 | fromFrontEndFd(0), toFrontEndFd(0), toPipeFrontPath(nullptr), fromPipeFrontPath(nullptr) { 23 | initPipeName(pipeDirPath); 24 | if(makePipe() != 0) { 25 | return; 26 | } 27 | if(openPipe() != 0) { 28 | return; 29 | } 30 | 31 | } 32 | 33 | PipeBackend::~PipeBackend() { 34 | if(toPipeFrontPath != nullptr) { 35 | delete toPipeFrontPath; 36 | } 37 | if(fromPipeFrontPath != nullptr) { 38 | delete fromPipeFrontPath; 39 | } 40 | if(pipeDirPath != nullptr) { 41 | delete pipeDirPath; 42 | pipeDirPath = nullptr; 43 | } 44 | } 45 | 46 | void PipeBackend::setPipeDirPath(const char *dirPath) { 47 | size_t dirPathLen = strlen(dirPath); 48 | pipeDirPath = new char[dirPathLen + 1]; 49 | memcpy(pipeDirPath, dirPath, dirPathLen + 1); 50 | } 51 | 52 | void PipeBackend::initPipeName(const char *dirPath) { 53 | if(dirPath == nullptr) { 54 | LOGE("dirPath is null"); 55 | exit(EXIT_FAILURE); 56 | } 57 | size_t dirPathLen = strlen(dirPath), toPipeFrontNameLen = strlen(TO_PIPE_FRONT_FILE_NAME), fromPipeFrontNameLen = strlen(FROM_PIPE_FRONT_FILE_NAME); 58 | size_t toPipeFrontPathLen = dirPathLen + toPipeFrontNameLen; 59 | size_t fromPipeFrontPathLen = dirPathLen + fromPipeFrontNameLen; 60 | this->toPipeFrontPath = new char[toPipeFrontPathLen + 2]; 61 | memcpy(this->toPipeFrontPath, dirPath, dirPathLen); 62 | this->toPipeFrontPath[dirPathLen] = '/'; 63 | memcpy(this->toPipeFrontPath + dirPathLen + 1, TO_PIPE_FRONT_FILE_NAME, toPipeFrontNameLen + 1); 64 | this->fromPipeFrontPath = new char[fromPipeFrontPathLen + 2]; 65 | memcpy(this->fromPipeFrontPath, dirPath, dirPathLen); 66 | this->fromPipeFrontPath[dirPathLen] = '/'; 67 | memcpy(this->fromPipeFrontPath + dirPathLen + 1, FROM_PIPE_FRONT_FILE_NAME, fromPipeFrontNameLen + 1); 68 | LOGI("toPipeFrontPath is %s", toPipeFrontPath); 69 | LOGI("fromPipeFrontPath is %s", fromPipeFrontPath); 70 | } 71 | 72 | int PipeBackend::readBytes(char* buf, int bufLen) { 73 | int readLen = 0; 74 | int tempRet = readLength(readLen); 75 | if(tempRet != 0) { 76 | return -1; 77 | } 78 | if(readLen > bufLen) { 79 | LOGE("target read length greater than buffer length"); 80 | return -1; 81 | } 82 | std::lock_guard lockGuard(PipeBackend::readMutex); 83 | int hasRead = 0; 84 | while(hasRead < readLen) { 85 | int tempReadLen = read(this->fromFrontEndFd, buf, readLen - hasRead); 86 | if(tempReadLen < 0) { 87 | LOGE("Fail to readBytes, %s", strerror(errno)); 88 | return -1; 89 | } 90 | hasRead += tempReadLen; 91 | } 92 | assert(hasRead == readLen); 93 | return readLen; 94 | } 95 | 96 | int PipeBackend::readBytes() { 97 | int readLen = 0; 98 | int tempRet = readLength(readLen); 99 | if(tempRet != 0) { 100 | return -1; 101 | } 102 | std::lock_guard lockGuard(PipeBackend::readMutex); 103 | int hasRead = 0; 104 | while(hasRead < readLen) { 105 | int tempReadLen = read(this->fromFrontEndFd, this->readBuffer, readLen - hasRead); 106 | if(tempReadLen < 0) { 107 | LOGE("Fail to readBytes, %s", strerror(errno)); 108 | return -1; 109 | } 110 | hasRead += tempReadLen; 111 | } 112 | return readLen; 113 | } 114 | 115 | int PipeBackend::readLength(int &len) { 116 | std::lock_guard lockGuard(PipeBackend::readMutex); 117 | int readLen = 0; 118 | int tempRet = read(this->fromFrontEndFd, &readLen, sizeof(readLen)); 119 | if(tempRet != 4) { 120 | LOGE("Fail to read readLen, %s", strerror(errno)); 121 | return -1; 122 | } 123 | if(readLen == 0) { 124 | LOGE("readLen equals zero, %s", strerror(errno)); 125 | return -1; 126 | } 127 | len = readLen; 128 | return 0; 129 | } 130 | 131 | int PipeBackend::readInt(int &x) { 132 | int readLen = 0; 133 | int tempRet = readLength(readLen); 134 | if(tempRet != 0) { 135 | return -1; 136 | } 137 | assert(readLen == 4); 138 | tempRet = readLength(x); 139 | if(tempRet != 0) { 140 | return -1; 141 | } 142 | return 0; 143 | } 144 | 145 | int PipeBackend::writeLength(int len) { 146 | std::lock_guard lockGuard(PipeBackend::writeMutex); 147 | int tempRet = write(this->toFrontEndFd, &len, sizeof(len)); 148 | if(tempRet != 4) { 149 | LOGE("Fail to write begin len, %s", strerror(errno)); 150 | return -1; 151 | } 152 | return 0; 153 | } 154 | 155 | int PipeBackend::writeInt(int dataInt) { 156 | int tempRet = writeLength(sizeof(int)); 157 | if(tempRet != 0) { 158 | return -1; 159 | } 160 | tempRet = writeLength(dataInt); 161 | if(tempRet != 0) { 162 | return -1; 163 | } 164 | return 0; 165 | } 166 | 167 | int PipeBackend::writeBytes(const char* msg, int len) { 168 | int tempRet = writeLength(len); 169 | if(tempRet < 0) { 170 | return -1; 171 | } 172 | std::lock_guard lockGuard(PipeBackend::writeMutex); 173 | int hasWritten = 0; 174 | while(hasWritten < len) { 175 | int tempWrite = write(this->toFrontEndFd, msg + hasWritten, (size_t)(len - hasWritten)); 176 | if(tempWrite < 0) { 177 | LOGE("Fail to writeBytes, %s", strerror(errno)); 178 | return -1; 179 | } 180 | hasWritten += tempWrite; 181 | } 182 | return 0; 183 | } 184 | 185 | 186 | 187 | int PipeBackend::openPipe() { 188 | int fromFrontFd = open(fromPipeFrontPath, O_RDWR | O_TRUNC); 189 | if(fromFrontFd == -1) { 190 | LOGE("Fail to open %s, %s", fromPipeFrontPath, strerror(errno)); 191 | } 192 | int toFrontFd = open(toPipeFrontPath, O_RDWR | O_TRUNC); 193 | if(toFrontFd == -1) { 194 | LOGE("Fail to open %s, %s", toPipeFrontPath, strerror(errno)); 195 | } 196 | if(fromFrontFd != -1 && toFrontFd != -1) { 197 | this->fromFrontEndFd = fromFrontFd; 198 | this->toFrontEndFd = toFrontFd; 199 | return 0; 200 | } 201 | else { 202 | return -1; 203 | } 204 | } 205 | 206 | int PipeBackend::makePipe() { 207 | int toFrontRet = mkfifo(toPipeFrontPath, 0666); 208 | int errno1 = 0, errno2 = 0; 209 | if(toFrontRet != 0) { 210 | errno1 = errno; 211 | if(errno == EEXIST) { 212 | LOGI("ToFrontPipeFile exists"); 213 | } 214 | else { 215 | LOGE("Fail to make %s, %s", toPipeFrontPath, strerror(errno)); 216 | } 217 | } 218 | int fromFrontRet = mkfifo(fromPipeFrontPath, 0666); 219 | if(fromFrontRet != 0 ) { 220 | errno2 = errno; 221 | if(errno == EEXIST) { 222 | LOGI("FromFrontPipeFile exists"); 223 | } 224 | else { 225 | LOGE("Fail to make %s, %s", fromPipeFrontPath, strerror(errno)); 226 | } 227 | 228 | } 229 | if((!toFrontRet || errno1 == EEXIST) && (!fromFrontRet || errno2 == EEXIST)) { 230 | LOGI("Success to make fifo"); 231 | LOGI("toFrontPipePath: %s", toPipeFrontPath); 232 | LOGI("fromFrontPipePath: %s", fromPipeFrontPath); 233 | return 0; 234 | } 235 | else { 236 | LOGE("Fail to create fifo"); 237 | return -1; 238 | } 239 | } -------------------------------------------------------------------------------- /app/src/main/java/com/gf/arrow4over6/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.gf.arrow4over6; 2 | 3 | import androidx.annotation.Nullable; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import android.content.Intent; 7 | import android.net.VpnService; 8 | import android.os.Bundle; 9 | import android.os.Handler; 10 | import android.os.Looper; 11 | import android.view.View; 12 | import android.widget.Button; 13 | import android.widget.EditText; 14 | import android.widget.TextView; 15 | 16 | import org.w3c.dom.Text; 17 | 18 | public class MainActivity extends AppCompatActivity { 19 | 20 | // state == 0, not connected; 1, connected; 2, being connecting 21 | private int connectButtonState = 0; 22 | 23 | private static String[] ipInfos; 24 | 25 | private static MainActivity _instance; 26 | 27 | // Used to load the 'native-lib' library on application startup. 28 | static { 29 | System.loadLibrary("arrowlib"); 30 | } 31 | 32 | static MainActivity getInstance() { 33 | return _instance; 34 | } 35 | 36 | public static void updateIpInfo(final String ipv4Addr) { 37 | Handler mainHandler = new Handler(Looper.getMainLooper()); 38 | mainHandler.post(new Runnable() { 39 | @Override 40 | public void run() { 41 | MainActivity mainActivity = getInstance(); 42 | TextView ipv4AddrView = mainActivity.findViewById(R.id.ipv4AddrView); 43 | ipv4AddrView.setText(ipv4Addr); 44 | } 45 | }); 46 | } 47 | 48 | public static void updateStatistics(final int connectTime, final int uploadSpeed, final int downloadSpeed, 49 | final int uploadPackets, final int downloadPackets, final int uploadFlow, final int downloadFlow) { 50 | final String connectTimeStr = connectTime + " s"; 51 | final String uploadSpeedStr = uploadSpeed + " Byte/s", downloadSpeedStr = downloadSpeed + " Byte/s"; 52 | final String uploadFlowStr = uploadFlow + " Bytes", downloadFlowStr = downloadFlow + " Bytes"; 53 | Handler mainHandler = new Handler(Looper.getMainLooper()); 54 | mainHandler.post(new Runnable() { 55 | @Override 56 | public void run() { 57 | MainActivity mainActivity = getInstance(); 58 | ((TextView) mainActivity.findViewById(R.id.connectTimeView)).setText(connectTimeStr); 59 | ((TextView) mainActivity.findViewById(R.id.uploadSpeedView)).setText(uploadSpeedStr); 60 | ((TextView) mainActivity.findViewById(R.id.downloadSpeedView)).setText(downloadSpeedStr); 61 | ((TextView) mainActivity.findViewById(R.id.uploadPacketsView)).setText(Integer.toString(uploadPackets)); 62 | ((TextView) mainActivity.findViewById(R.id.downloadPacketsView)).setText(Integer.toString(downloadSpeed)); 63 | ((TextView) mainActivity.findViewById(R.id.uploadFlowView)).setText(uploadFlowStr); 64 | ((TextView) mainActivity.findViewById(R.id.downloadFlowView)).setText(downloadFlowStr); 65 | } 66 | }); 67 | } 68 | 69 | public static void clearStatistics() { 70 | Handler mainHandler = new Handler(Looper.getMainLooper()); 71 | mainHandler.post(new Runnable() { 72 | @Override 73 | public void run() { 74 | MainActivity mainActivity = getInstance(); 75 | ((TextView) mainActivity.findViewById(R.id.ipv4AddrView)).setText(""); 76 | ((TextView) mainActivity.findViewById(R.id.connectTimeView)).setText(""); 77 | ((TextView) mainActivity.findViewById(R.id.uploadSpeedView)).setText(""); 78 | ((TextView) mainActivity.findViewById(R.id.downloadSpeedView)).setText(""); 79 | ((TextView) mainActivity.findViewById(R.id.uploadPacketsView)).setText(""); 80 | ((TextView) mainActivity.findViewById(R.id.downloadPacketsView)).setText(""); 81 | ((TextView) mainActivity.findViewById(R.id.uploadFlowView)).setText(""); 82 | ((TextView) mainActivity.findViewById(R.id.downloadFlowView)).setText(""); 83 | // ((Button) mainActivity.findViewById(R.id.connectButton)).setText("连接"); 84 | // connectButtonState = 0; 85 | } 86 | }); 87 | } 88 | 89 | public static void establishVpn(final String[] ipInfos) { 90 | MainActivity.ipInfos = ipInfos; 91 | Handler mainHandler = new Handler(Looper.getMainLooper()); 92 | mainHandler.post(new Runnable() { 93 | @Override 94 | public void run() { 95 | ArLog.i("Start to establish Vpn"); 96 | 97 | Intent intent = VpnService.prepare(MainActivity.getInstance()); 98 | if(intent != null) { 99 | getInstance().startActivityForResult(intent, Constants.VPN_REQUEST_CODE); 100 | 101 | } 102 | else { 103 | 104 | getInstance().onActivityResult(Constants.VPN_REQUEST_CODE, RESULT_OK, null); 105 | } 106 | } 107 | }); 108 | 109 | } 110 | 111 | @Override 112 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 113 | super.onActivityResult(requestCode, resultCode, data); 114 | ArLog.i("onActivityResult begin"); 115 | if(requestCode == Constants.VPN_REQUEST_CODE && resultCode == RESULT_OK) { 116 | Intent startVpnIntent = new Intent(this, ArrowVpnService.class); 117 | startVpnIntent.putExtra("ipInfos", ipInfos); 118 | startService(startVpnIntent); 119 | } 120 | else { 121 | ArLog.w("requestCode = " + requestCode + " resultCode = " + resultCode); 122 | } 123 | } 124 | 125 | private void testPipe() { 126 | new Thread(new Runnable() { 127 | @Override 128 | public void run() { 129 | PipeFrontEnd.waitUntilInitialized(); 130 | int receiveInt = PipeFrontEnd.getInstance().readInt(); 131 | ArLog.i("Read int from backend: " + receiveInt); 132 | int sendInt = 102; 133 | PipeFrontEnd.getInstance().writeInt(sendInt); 134 | } 135 | }).start(); 136 | 137 | } 138 | 139 | private void connectVpn() { 140 | String ipv6AddrStr = ((EditText)findViewById(R.id.ipv6AddrText)).getText().toString(); 141 | int port = Integer.parseInt(((EditText)findViewById(R.id.portText)).getText().toString()); 142 | ArFrontFramework.getInstance().establishConnect(ipv6AddrStr, port); 143 | } 144 | 145 | private void stopConnect() { 146 | PipeFrontEnd.getInstance().writeInt(103); 147 | MainActivity.clearStatistics(); 148 | } 149 | 150 | private void setConnectButton() { 151 | final Button connectButton = (Button) findViewById(R.id.connectButton); 152 | ArLog.i("connectButton clicked"); 153 | connectButton.setOnClickListener(new View.OnClickListener() { 154 | @Override 155 | public void onClick(View view) { 156 | if(connectButtonState == 0) { 157 | ArLog.i("turn into connecting state"); 158 | connectButton.setText("断开"); 159 | connectButtonState = 1; 160 | connectVpn(); 161 | } 162 | else if(connectButtonState == 1) { 163 | ArLog.i("turn into disconnected state"); 164 | connectButton.setText("连接"); 165 | connectButtonState = 0; 166 | stopConnect(); 167 | } 168 | } 169 | }); 170 | } 171 | 172 | private void initView() { 173 | EditText ipv6AddrText = (EditText) findViewById(R.id.ipv6AddrText); 174 | ipv6AddrText.setText(Constants.INIT_IPV6_ADDRESS); 175 | EditText portText = (EditText) findViewById(R.id.portText); 176 | portText.setText(Constants.INIT_PORT); 177 | setConnectButton(); 178 | } 179 | 180 | @Override 181 | protected void onCreate(Bundle savedInstanceState) { 182 | super.onCreate(savedInstanceState); 183 | setContentView(R.layout.activity_main); 184 | _instance = this; 185 | // Example of a call to a native method 186 | int backendRet = startBackend(getFilesDir().getAbsolutePath()); 187 | PipeFrontEnd.setPipeDirPathAndBuild(getFilesDir().getAbsolutePath()); 188 | ArLog.i("After set pipe dir"); 189 | // PipeFrontEnd.constructInstance(); 190 | // testPipe(); 191 | initView(); 192 | ArLog.i("haha"); 193 | } 194 | 195 | /** 196 | * A native method that is implemented by the 'native-lib' native library, 197 | * which is packaged with this application. 198 | */ 199 | public native int startBackend(String appDir); 200 | } 201 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |