Skip to content

Commit 536e050

Browse files
committed
feat: integrate LibXray VPN service and update build configuration
1 parent b5c505d commit 536e050

4 files changed

Lines changed: 376 additions & 64 deletions

File tree

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ios/Frameworks/LibXray.xcframework/**/LibXray filter=lfs diff=lfs merge=lfs -text
2+
*.apk filter=lfs diff=lfs merge=lfs -text
3+
*.so filter=lfs diff=lfs merge=lfs -text

android/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ android {
5454
testImplementation("org.mockito:mockito-core:5.0.0")
5555
//implementation project(':vpnclient-engine-android')
5656
//implementation 'com.github.VPNclient:VPNclient-engine:android-v0.1.1'
57+
58+
// LibXray dependencies
59+
implementation files("libs/libXray.aar")
5760
}
5861

5962
testOptions {
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package click.vpnclient.engine.flutter.vpnclient_engine_flutter
2+
3+
import android.app.Notification
4+
import android.app.NotificationChannel
5+
import android.app.NotificationManager
6+
import android.content.Intent
7+
import android.net.VpnService
8+
import android.os.Binder
9+
import android.os.Build
10+
import android.os.IBinder
11+
import android.os.ParcelFileDescriptor
12+
import android.util.Log
13+
import libxray.Libxray
14+
import java.io.File
15+
import java.io.FileOutputStream
16+
17+
/**
18+
* VPN Service that integrates with libXray
19+
*/
20+
class LibXrayVpnService : VpnService() {
21+
private val TAG = "LibXrayVpnService"
22+
private val CHANNEL_ID = "VPN_CHANNEL"
23+
private val NOTIFICATION_ID = 1
24+
25+
private var vpnInterface: ParcelFileDescriptor? = null
26+
private var isVpnRunning: Boolean = false
27+
private val binder = LocalBinder()
28+
29+
/**
30+
* Local binder for binding to the service
31+
*/
32+
inner class LocalBinder : Binder() {
33+
fun getService(): LibXrayVpnService = this@LibXrayVpnService
34+
}
35+
36+
override fun onBind(intent: Intent?): IBinder {
37+
return binder
38+
}
39+
40+
override fun onCreate() {
41+
super.onCreate()
42+
createNotificationChannel()
43+
Log.d(TAG, "LibXrayVpnService created")
44+
}
45+
46+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
47+
when (intent?.action) {
48+
ACTION_CONNECT -> {
49+
val config = intent.getStringExtra(EXTRA_CONFIG)
50+
val datDir = intent.getStringExtra(EXTRA_DAT_DIR)
51+
if (config != null && datDir != null) {
52+
startVpn(config, datDir)
53+
}
54+
}
55+
ACTION_DISCONNECT -> {
56+
stopVpn()
57+
}
58+
}
59+
return START_STICKY
60+
}
61+
62+
/**
63+
* Start VPN connection with libXray
64+
*/
65+
private fun startVpn(configJson: String, datDir: String) {
66+
try {
67+
Log.d(TAG, "Starting VPN with libXray")
68+
69+
// Create notification for foreground service
70+
val notification = createNotification()
71+
startForeground(NOTIFICATION_ID, notification)
72+
73+
// Setup VPN interface
74+
val builder = Builder()
75+
.setSession("LibXray VPN")
76+
.setMtu(1500)
77+
.addAddress("10.0.0.1", 32)
78+
.addRoute("0.0.0.0", 0)
79+
.addDnsServer("8.8.8.8")
80+
.addDnsServer("8.8.4.4")
81+
82+
// Establish VPN interface
83+
vpnInterface = builder.establish()
84+
85+
if (vpnInterface == null) {
86+
Log.e(TAG, "Failed to establish VPN interface")
87+
stopSelf()
88+
return
89+
}
90+
91+
// Initialize libXray environment
92+
Libxray.initEnv(datDir)
93+
94+
// Register dialer controller for socket protection
95+
val dialerController = object : libxray.DialerController {
96+
override fun protectFd(fd: Long): Boolean {
97+
return protect(fd.toInt())
98+
}
99+
}
100+
Libxray.registerDialerController(dialerController)
101+
Libxray.registerListenerController(dialerController)
102+
103+
// Start Xray with the provided configuration
104+
Libxray.runXrayFromJSON(datDir, configJson)
105+
106+
isVpnRunning = true
107+
Log.d(TAG, "VPN started successfully")
108+
109+
} catch (e: Exception) {
110+
Log.e(TAG, "Error starting VPN", e)
111+
stopVpn()
112+
}
113+
}
114+
115+
/**
116+
* Stop VPN connection
117+
*/
118+
private fun stopVpn() {
119+
try {
120+
Log.d(TAG, "Stopping VPN")
121+
122+
// Stop Xray
123+
if (isVpnRunning) {
124+
Libxray.stopXray()
125+
}
126+
127+
// Close VPN interface
128+
vpnInterface?.close()
129+
vpnInterface = null
130+
131+
isVpnRunning = false
132+
133+
// Stop foreground service
134+
stopForeground(STOP_FOREGROUND_REMOVE)
135+
stopSelf()
136+
137+
Log.d(TAG, "VPN stopped successfully")
138+
139+
} catch (e: Exception) {
140+
Log.e(TAG, "Error stopping VPN", e)
141+
}
142+
}
143+
144+
/**
145+
* Check if VPN is currently running
146+
*/
147+
fun isVpnRunning(): Boolean {
148+
return isVpnRunning && Libxray.getXrayState()
149+
}
150+
151+
/**
152+
* Create notification channel for Android O+
153+
*/
154+
private fun createNotificationChannel() {
155+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
156+
val name = "VPN Service"
157+
val descriptionText = "VPN connection notification"
158+
val importance = NotificationManager.IMPORTANCE_LOW
159+
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
160+
description = descriptionText
161+
}
162+
163+
val notificationManager: NotificationManager =
164+
getSystemService(NOTIFICATION_SERVICE) as NotificationManager
165+
notificationManager.createNotificationChannel(channel)
166+
}
167+
}
168+
169+
/**
170+
* Create notification for foreground service
171+
*/
172+
private fun createNotification(): Notification {
173+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
174+
Notification.Builder(this, CHANNEL_ID)
175+
.setContentTitle("VPN Connected")
176+
.setContentText("LibXray VPN is running")
177+
.setSmallIcon(android.R.drawable.ic_dialog_info)
178+
.setOngoing(true)
179+
.build()
180+
} else {
181+
@Suppress("DEPRECATION")
182+
Notification.Builder(this)
183+
.setContentTitle("VPN Connected")
184+
.setContentText("LibXray VPN is running")
185+
.setSmallIcon(android.R.drawable.ic_dialog_info)
186+
.setOngoing(true)
187+
.build()
188+
}
189+
}
190+
191+
override fun onDestroy() {
192+
stopVpn()
193+
super.onDestroy()
194+
Log.d(TAG, "LibXrayVpnService destroyed")
195+
}
196+
197+
companion object {
198+
const val ACTION_CONNECT = "click.vpnclient.engine.flutter.ACTION_CONNECT"
199+
const val ACTION_DISCONNECT = "click.vpnclient.engine.flutter.ACTION_DISCONNECT"
200+
const val EXTRA_CONFIG = "extra_config"
201+
const val EXTRA_DAT_DIR = "extra_dat_dir"
202+
}
203+
}

0 commit comments

Comments
 (0)