Add bridge support

This commit is contained in:
世界
2026-07-07 22:18:22 +08:00
parent 03a4f2c5e1
commit 3657ab7d6c
5 changed files with 129 additions and 0 deletions
@@ -0,0 +1,11 @@
package io.nekohasekai.sfa.bg;
import android.os.ParcelFileDescriptor;
interface IBridgeSession {
ParcelFileDescriptor getFileDescriptor();
String getName();
boolean isInet6Active();
void setEgress(String interfaceName);
void close();
}
@@ -1,6 +1,7 @@
package io.nekohasekai.sfa.bg; package io.nekohasekai.sfa.bg;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import io.nekohasekai.sfa.bg.IBridgeSession;
import io.nekohasekai.sfa.bg.INeighborTableCallback; import io.nekohasekai.sfa.bg.INeighborTableCallback;
import io.nekohasekai.sfa.bg.IRootShellSession; import io.nekohasekai.sfa.bg.IRootShellSession;
import io.nekohasekai.sfa.bg.ParceledListSlice; import io.nekohasekai.sfa.bg.ParceledListSlice;
@@ -21,4 +22,6 @@ interface IRootService {
IRootShellSession openShellSession(String user, String command, in String[] env, String term, int rows, int cols) = 6; IRootShellSession openShellSession(String user, String command, in String[] env, String term, int rows, int cols) = 6;
String lookupSFTPServer() = 7; String lookupSFTPServer() = 7;
IBridgeSession openBridge(String bridgeName, int mtu, String inet4Port, String inet6Port, int ruleIndex, int routeTable) = 8;
} }
@@ -9,6 +9,8 @@ import android.provider.Settings
import android.system.OsConstants import android.system.OsConstants
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import io.nekohasekai.libbox.BridgeOptions
import io.nekohasekai.libbox.BridgeSession
import io.nekohasekai.libbox.ConnectionOwner import io.nekohasekai.libbox.ConnectionOwner
import io.nekohasekai.libbox.InterfaceUpdateListener import io.nekohasekai.libbox.InterfaceUpdateListener
import io.nekohasekai.libbox.Libbox import io.nekohasekai.libbox.Libbox
@@ -279,6 +281,25 @@ interface PlatformInterfaceWrapper : PlatformInterface {
)?.takeIf { it.isNotBlank() } )?.takeIf { it.isNotBlank() }
?: "${Build.MANUFACTURER} ${Build.MODEL}" ?: "${Build.MANUFACTURER} ${Build.MODEL}"
override fun usePlatformBridge(): Boolean = RootClient.rootAvailable.value ?: runBlocking(Dispatchers.IO) {
RootClient.checkRootAvailable()
}
override fun createBridge(options: BridgeOptions?): BridgeSession {
options!!
val session = runBlocking(Dispatchers.IO) {
RootClient.openBridge(
options.bridgeName,
options.mtu,
options.inet4Port,
options.inet6Port,
options.ruleIndex,
options.routeTable,
)
}
return RootBridgeSessionWrapper(session)
}
override fun lookupUser(username: String?): io.nekohasekai.libbox.PlatformUser { override fun lookupUser(username: String?): io.nekohasekai.libbox.PlatformUser {
val resolved = UserResolver.resolve(Application.packageManager, username!!) val resolved = UserResolver.resolve(Application.packageManager, username!!)
val platformUser = io.nekohasekai.libbox.PlatformUser() val platformUser = io.nekohasekai.libbox.PlatformUser()
@@ -300,6 +321,24 @@ interface PlatformInterfaceWrapper : PlatformInterface {
} }
} }
private class RootBridgeSessionWrapper(
private val session: IBridgeSession,
) : BridgeSession {
override fun fileDescriptor(): Int = session.fileDescriptor.detachFd()
override fun name(): String = session.name
override fun inet6Active(): Boolean = session.isInet6Active
override fun setEgress(interfaceName: String?) {
session.setEgress(interfaceName ?: "")
}
override fun close() {
session.close()
}
}
private class RootShellSessionWrapper( private class RootShellSessionWrapper(
private val rootSession: IRootShellSession, private val rootSession: IRootShellSession,
) : ShellSession { ) : ShellSession {
@@ -169,6 +169,22 @@ object RootClient {
} }
} }
suspend fun openBridge(
bridgeName: String,
mtu: Int,
inet4Port: String,
inet6Port: String,
ruleIndex: Int,
routeTable: Int,
): IBridgeSession {
val svc = bindService()
try {
return svc.openBridge(bridgeName, mtu, inet4Port, inet6Port, ruleIndex, routeTable)
} catch (e: RemoteException) {
throw e.rethrowAsRuntime()
}
}
suspend fun unregisterNeighborTableCallback(callback: INeighborTableCallback) { suspend fun unregisterNeighborTableCallback(callback: INeighborTableCallback) {
try { try {
service?.unregisterNeighborTableCallback(callback) service?.unregisterNeighborTableCallback(callback)
@@ -9,6 +9,8 @@ import android.os.ParcelFileDescriptor
import android.os.RemoteCallbackList import android.os.RemoteCallbackList
import android.util.Log import android.util.Log
import com.topjohnwu.superuser.ipc.RootService import com.topjohnwu.superuser.ipc.RootService
import io.nekohasekai.libbox.BridgeOptions
import io.nekohasekai.libbox.BridgeSession
import io.nekohasekai.libbox.Libbox import io.nekohasekai.libbox.Libbox
import io.nekohasekai.libbox.NeighborEntryIterator import io.nekohasekai.libbox.NeighborEntryIterator
import io.nekohasekai.libbox.NeighborSubscription import io.nekohasekai.libbox.NeighborSubscription
@@ -36,6 +38,8 @@ class RootServer : RootService() {
private var tetheringCallback: Any? = null private var tetheringCallback: Any? = null
private var tetheringManager: Any? = null private var tetheringManager: Any? = null
private val bridgeSessions = mutableSetOf<BridgeSessionBinder>()
private val binder = object : IRootService.Stub() { private val binder = object : IRootService.Stub() {
override fun destroy() { override fun destroy() {
stopSelf() stopSelf()
@@ -178,6 +182,29 @@ class RootServer : RootService() {
return RootShellSession(session) return RootShellSession(session)
} }
override fun openBridge(
bridgeName: String?,
mtu: Int,
inet4Port: String?,
inet6Port: String?,
ruleIndex: Int,
routeTable: Int,
): IBridgeSession {
val options = BridgeOptions()
options.bridgeName = bridgeName ?: ""
options.mtu = mtu
options.inet4Port = inet4Port ?: ""
options.inet6Port = inet6Port ?: ""
options.ruleIndex = ruleIndex
options.routeTable = routeTable
val session = Libbox.newBridgeService(options)
val binder = BridgeSessionBinder(session)
synchronized(bridgeSessions) {
bridgeSessions.add(binder)
}
return binder
}
override fun lookupSFTPServer(): String { override fun lookupSFTPServer(): String {
val termuxPrefix = File(UserResolver.TERMUX_PREFIX) val termuxPrefix = File(UserResolver.TERMUX_PREFIX)
for (name in arrayOf("libexec/sftp-server", "lib/openssh/sftp-server")) { for (name in arrayOf("libexec/sftp-server", "lib/openssh/sftp-server")) {
@@ -219,6 +246,28 @@ class RootServer : RootService() {
return env.map { (k, v) -> "$k=$v" }.toTypedArray() return env.map { (k, v) -> "$k=$v" }.toTypedArray()
} }
private inner class BridgeSessionBinder(
private val session: BridgeSession,
) : IBridgeSession.Stub() {
override fun getFileDescriptor(): ParcelFileDescriptor = ParcelFileDescriptor.fromFd(session.fileDescriptor())
override fun getName(): String = session.name()
override fun isInet6Active(): Boolean = session.inet6Active()
override fun setEgress(interfaceName: String?) {
session.setEgress(interfaceName ?: "")
}
override fun close() {
synchronized(bridgeSessions) {
bridgeSessions.remove(this)
}
session.close()
}
}
private class RootShellSession( private class RootShellSession(
private val session: ShellSession, private val session: ShellSession,
) : IRootShellSession.Stub() { ) : IRootShellSession.Stub() {
@@ -363,6 +412,17 @@ class RootServer : RootService() {
override fun onBind(intent: Intent): IBinder = binder override fun onBind(intent: Intent): IBinder = binder
override fun onDestroy() { override fun onDestroy() {
val sessions = synchronized(bridgeSessions) {
val copied = bridgeSessions.toList()
bridgeSessions.clear()
copied
}
for (session in sessions) {
try {
session.close()
} catch (_: Exception) {
}
}
stopTetheringMonitor() stopTetheringMonitor()
neighborSubscription?.close() neighborSubscription?.close()
neighborSubscription = null neighborSubscription = null