An Android example application demonstrating the integration of Invisv's pseudotcp library using Android's VPN APIs and gomobile bindings.
This application demonstrates how to implement a VPN service in Android that routes traffic through a MASQUE proxy using the pseudotcp library. It showcases:
- Integration with Android's VPN Service API
- Go-to-Java bindings using gomobile
- Implementation of pseudotcp for reliable transport
- MASQUE client for proxy communication
- Android Studio (latest version recommended)
- Go 1.23 or later
- gomobile
- Docker (for testing)
- Android SDK with API level 33 or higher
The Go code is built with gomobile. Use the provided build script:
$ cd app/src/go
$ ./build.sh
This will generate an .aar
file and move it to the appropriate Android libs directory.
Important: After building the Go code, remember to sync your project with Gradle files in Android Studio to reload the newly built library.
Open the project in Android Studio and build the app using the standard build process:
$ ./gradlew assembleDebug
- Install the app on your Android device or emulator
- Launch the app
- Enter the proxy IP and port (defaults to 127.0.0.1:8444)
- Toggle the switch to enable/disable the VPN service
- The app will route all traffic through the specified MASQUE proxy
This repo includes a comprehensive end-to-end test that exercises the entire network stack.
┌──────────────────────────────┐ ┌──────────────────────────────────┐
│ │ │ Docker containers │
│ Android Emulator (qemu) │ │ ┌─────────────────────────┐ │
│ │ │ │ Echo server │ │
│ ┌───────────────────────┐ │ │ │ │ │
│ │ PseudotcpExampleApp │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ └─────────┬─────────────┘ │ │ │ 172.25.0.4 │ │
│ │ │ │ │ │ │
│ ┌─────────▼─────────────┐ │ │ │ │ │
│ │ gomobile bindings │ │ │ │ │ │
│ │ │ │ │ └───────────▲─────────────┘ │
│ └─────────┬─────────────┘ │ │ │ │
│ │ │ │ │ │
│ ┌─────────▼─────────────┐ │ │ ┌───────────┼─────────────┐ │
│ │ pseudotcp │ │ │ │ h2o MASQUE Proxy │ │
│ │ │ │ │ │ │ │
│ └─────────┬─────────────┘ │ │ │ │ │
│ │ │ │ │ │ │
│ ┌─────────▼─────────────┐ │ │ │ 172.25.0.3 │ │
│ │ masque client │ │ │ │ │ │
│ │ ┼───┼───────────┼──►│ │ │
│ └───────────────────────┘ │ │ └─────────────────────────┘ │
└──────────────────────────────┘ └──────────────────────────────────┘
The emulator uses qemu and a base android image to create a virtualized android device. We then use the uiautomator tool to perform actions on the device, replicating actual user usage.
We use docker to run an h2o MASQUE proxy and another very simple echo server. The echo server responds to HTTP requests with information about the HTTP request, including the request IP.
Inside the automated test we can then start, check our initial reported IP, enable our sample app service, check our reported IP, and assert that the new reported IP is that of the proxy, proving that packets from the android host device are now passing through the MASQUE proxy as expected.
Start the dockerized h2o server and echo server:
$ docker-compose up -d
The docker-compose file creates a custom network with subnet 172.25.0.0/24
. If this conflicts with your network, update the addresses in both the docker-compose file and the end2end test.
You can run tests through Android Studio or from the command line:
$ ./gradlew connectedAndroidTest
An HTML report will be generated at:
app/build/reports/androidTests/connected/debug/com.invisv.pseudotcpexampleapp.End2EndTest.html
To capture logs during test execution:
$ adb logcat "System.out:D End2EndTest:D *:S" & LOGCAT_PID=$! ; \
./gradlew connectedAndroidTest ; \
test_ret=$? ; \
if [ -n "$LOGCAT_PID" ] ; then kill $LOGCAT_PID; fi; \
exit $test_ret