diff --git a/app/src/main/aidl/io/nekohasekai/sfa/bg/IBridgeSession.aidl b/app/src/main/aidl/io/nekohasekai/sfa/bg/IBridgeSession.aidl new file mode 100644 index 0000000..5ec9751 --- /dev/null +++ b/app/src/main/aidl/io/nekohasekai/sfa/bg/IBridgeSession.aidl @@ -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(); +} diff --git a/app/src/main/aidl/io/nekohasekai/sfa/bg/IRootService.aidl b/app/src/main/aidl/io/nekohasekai/sfa/bg/IRootService.aidl index ad0425a..e77818b 100644 --- a/app/src/main/aidl/io/nekohasekai/sfa/bg/IRootService.aidl +++ b/app/src/main/aidl/io/nekohasekai/sfa/bg/IRootService.aidl @@ -1,6 +1,7 @@ package io.nekohasekai.sfa.bg; import android.os.ParcelFileDescriptor; +import io.nekohasekai.sfa.bg.IBridgeSession; import io.nekohasekai.sfa.bg.INeighborTableCallback; import io.nekohasekai.sfa.bg.IRootShellSession; 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; String lookupSFTPServer() = 7; + + IBridgeSession openBridge(String bridgeName, int mtu, String inet4Port, String inet6Port, int ruleIndex, int routeTable) = 8; } diff --git a/app/src/main/java/io/nekohasekai/sfa/bg/PlatformInterfaceWrapper.kt b/app/src/main/java/io/nekohasekai/sfa/bg/PlatformInterfaceWrapper.kt index dff58cc..afe2628 100644 --- a/app/src/main/java/io/nekohasekai/sfa/bg/PlatformInterfaceWrapper.kt +++ b/app/src/main/java/io/nekohasekai/sfa/bg/PlatformInterfaceWrapper.kt @@ -9,6 +9,8 @@ import android.provider.Settings import android.system.OsConstants import android.util.Log import androidx.annotation.RequiresApi +import io.nekohasekai.libbox.BridgeOptions +import io.nekohasekai.libbox.BridgeSession import io.nekohasekai.libbox.ConnectionOwner import io.nekohasekai.libbox.InterfaceUpdateListener import io.nekohasekai.libbox.Libbox @@ -279,6 +281,25 @@ interface PlatformInterfaceWrapper : PlatformInterface { )?.takeIf { it.isNotBlank() } ?: "${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 { val resolved = UserResolver.resolve(Application.packageManager, username!!) 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 val rootSession: IRootShellSession, ) : ShellSession { diff --git a/app/src/main/java/io/nekohasekai/sfa/bg/RootClient.kt b/app/src/main/java/io/nekohasekai/sfa/bg/RootClient.kt index fe2977d..6a158c7 100644 --- a/app/src/main/java/io/nekohasekai/sfa/bg/RootClient.kt +++ b/app/src/main/java/io/nekohasekai/sfa/bg/RootClient.kt @@ -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) { try { service?.unregisterNeighborTableCallback(callback) diff --git a/app/src/main/java/io/nekohasekai/sfa/bg/RootServer.kt b/app/src/main/java/io/nekohasekai/sfa/bg/RootServer.kt index 38eaf8d..92e2a2e 100644 --- a/app/src/main/java/io/nekohasekai/sfa/bg/RootServer.kt +++ b/app/src/main/java/io/nekohasekai/sfa/bg/RootServer.kt @@ -9,6 +9,8 @@ import android.os.ParcelFileDescriptor import android.os.RemoteCallbackList import android.util.Log 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.NeighborEntryIterator import io.nekohasekai.libbox.NeighborSubscription @@ -36,6 +38,8 @@ class RootServer : RootService() { private var tetheringCallback: Any? = null private var tetheringManager: Any? = null + private val bridgeSessions = mutableSetOf() + private val binder = object : IRootService.Stub() { override fun destroy() { stopSelf() @@ -178,6 +182,29 @@ class RootServer : RootService() { 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 { val termuxPrefix = File(UserResolver.TERMUX_PREFIX) 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() } + 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 val session: ShellSession, ) : IRootShellSession.Stub() { @@ -363,6 +412,17 @@ class RootServer : RootService() { override fun onBind(intent: Intent): IBinder = binder override fun onDestroy() { + val sessions = synchronized(bridgeSessions) { + val copied = bridgeSessions.toList() + bridgeSessions.clear() + copied + } + for (session in sessions) { + try { + session.close() + } catch (_: Exception) { + } + } stopTetheringMonitor() neighborSubscription?.close() neighborSubscription = null