@@ -2,15 +2,36 @@ package io.nekohasekai.sfa.bg
22
33import android.content.Intent
44import android.content.pm.PackageInfo
5+ import android.os.Build
56import android.os.IBinder
67import android.os.ParcelFileDescriptor
8+ import android.os.RemoteCallbackList
9+ import android.util.Log
710import com.topjohnwu.superuser.ipc.RootService
11+ import io.nekohasekai.libbox.Libbox
12+ import io.nekohasekai.libbox.NeighborEntryIterator
13+ import io.nekohasekai.libbox.NeighborSubscription
14+ import io.nekohasekai.libbox.NeighborUpdateListener
815import io.nekohasekai.sfa.BuildConfig
916import io.nekohasekai.sfa.vendor.PrivilegedServiceUtils
1017import java.io.IOException
18+ import java.lang.reflect.Proxy
19+ import java.util.concurrent.ConcurrentHashMap
20+ import java.util.concurrent.Executors
1121
1222class RootServer : RootService () {
1323
24+ private val neighborCallbacks = RemoteCallbackList <INeighborTableCallback >()
25+ private var neighborSubscription: NeighborSubscription ? = null
26+
27+ private val hostnameByMAC = ConcurrentHashMap <String , String >()
28+
29+ @Volatile
30+ private var lastNeighborEntries: List <Pair <String , String >>? = null
31+
32+ private var tetheringCallback: Any? = null
33+ private var tetheringManager: Any? = null
34+
1435 private val binder = object : IRootService .Stub () {
1536 override fun destroy () {
1637 stopSelf()
@@ -31,7 +52,172 @@ class RootServer : RootService() {
3152 outputPath!! ,
3253 BuildConfig .APPLICATION_ID ,
3354 )
55+
56+ override fun registerNeighborTableCallback (callback : INeighborTableCallback ? ) {
57+ if (callback == null ) return
58+ neighborCallbacks.register(callback)
59+ synchronized(neighborCallbacks) {
60+ if (neighborSubscription == null ) {
61+ try {
62+ neighborSubscription =
63+ Libbox .subscribeNeighborTable(object : NeighborUpdateListener {
64+ override fun updateNeighborTable (entries : NeighborEntryIterator ? ) {
65+ if (entries == null ) return
66+ val rawList = mutableListOf<Pair <String , String >>()
67+ while (entries.hasNext()) {
68+ val entry = entries.next()
69+ rawList.add(entry.address to entry.macAddress)
70+ }
71+ lastNeighborEntries = rawList
72+ broadcastEnrichedEntries(rawList)
73+ }
74+ })
75+ } catch (e: Exception ) {
76+ Log .e(" RootServer" , " subscribeNeighborTable failed" , e)
77+ }
78+ startTetheringMonitor()
79+ }
80+ }
81+ }
82+
83+ override fun unregisterNeighborTableCallback (callback : INeighborTableCallback ? ) {
84+ if (callback == null ) return
85+ neighborCallbacks.unregister(callback)
86+ synchronized(neighborCallbacks) {
87+ if (neighborCallbacks.registeredCallbackCount == 0 ) {
88+ neighborSubscription?.close()
89+ neighborSubscription = null
90+ stopTetheringMonitor()
91+ }
92+ }
93+ }
94+ }
95+
96+ private fun broadcastEnrichedEntries (rawList : List <Pair <String , String >>) {
97+ val list = rawList.map { (address, mac) ->
98+ NeighborEntry (address, mac, hostnameByMAC[mac.uppercase()] ? : " " )
99+ }
100+ Log .d(" RootServer" , " neighborTable updated: ${list.size} entries" )
101+ val slice = ParceledListSlice (list)
102+ val count = neighborCallbacks.beginBroadcast()
103+ try {
104+ repeat(count) { i ->
105+ try {
106+ neighborCallbacks.getBroadcastItem(i).onNeighborTableUpdated(slice)
107+ } catch (_: Exception ) {
108+ }
109+ }
110+ } finally {
111+ neighborCallbacks.finishBroadcast()
112+ }
113+ }
114+
115+ // TetheringManager reflection (API 30+)
116+
117+ private val classTetheredClient by lazy {
118+ Class .forName(" android.net.TetheredClient" )
119+ }
120+ private val getMacAddress by lazy {
121+ classTetheredClient.getDeclaredMethod(" getMacAddress" )
122+ }
123+ private val getAddresses by lazy {
124+ classTetheredClient.getDeclaredMethod(" getAddresses" )
125+ }
126+ private val classAddressInfo by lazy {
127+ Class .forName(" android.net.TetheredClient\$ AddressInfo" )
128+ }
129+ private val getHostname by lazy {
130+ classAddressInfo.getDeclaredMethod(" getHostname" )
131+ }
132+
133+ private fun startTetheringMonitor () {
134+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .R ) return
135+ try {
136+ val manager = getSystemService(" tethering" ) ? : return
137+ tetheringManager = manager
138+ val callbackClass =
139+ Class .forName(" android.net.TetheringManager\$ TetheringEventCallback" )
140+ val registerMethod = manager.javaClass.getMethod(
141+ " registerTetheringEventCallback" ,
142+ java.util.concurrent.Executor ::class .java,
143+ callbackClass,
144+ )
145+ val proxy = Proxy .newProxyInstance(
146+ callbackClass.classLoader,
147+ arrayOf(callbackClass),
148+ ) { proxyObject, method, args ->
149+ when (method.name) {
150+ " hashCode" -> System .identityHashCode(proxyObject)
151+ " equals" -> proxyObject == = args?.get(0 )
152+ " toString" -> proxyObject.javaClass.name + " @" +
153+ Integer .toHexString(System .identityHashCode(proxyObject))
154+ " onClientsChanged" -> {
155+ if (args != null ) {
156+ @Suppress(" UNCHECKED_CAST" )
157+ handleClientsChanged(args[0 ] as Collection <* >)
158+ }
159+ null
160+ }
161+ else -> null
162+ }
163+ }
164+ tetheringCallback = proxy
165+ registerMethod.invoke(manager, Executors .newSingleThreadExecutor(), proxy)
166+ Log .d(" RootServer" , " TetheringManager monitor started" )
167+ } catch (e: Exception ) {
168+ Log .e(" RootServer" , " startTetheringMonitor failed" , e)
169+ }
170+ }
171+
172+ private fun stopTetheringMonitor () {
173+ val manager = tetheringManager ? : return
174+ val callback = tetheringCallback ? : return
175+ try {
176+ val callbackClass =
177+ Class .forName(" android.net.TetheringManager\$ TetheringEventCallback" )
178+ val unregisterMethod = manager.javaClass.getMethod(
179+ " unregisterTetheringEventCallback" ,
180+ callbackClass,
181+ )
182+ unregisterMethod.invoke(manager, callback)
183+ } catch (e: Exception ) {
184+ Log .e(" RootServer" , " stopTetheringMonitor failed" , e)
185+ }
186+ tetheringCallback = null
187+ tetheringManager = null
188+ hostnameByMAC.clear()
189+ }
190+
191+ private fun handleClientsChanged (clients : Collection <* >) {
192+ hostnameByMAC.clear()
193+ for (client in clients) {
194+ if (client == null ) continue
195+ try {
196+ val mac = getMacAddress.invoke(client).toString().uppercase()
197+ @Suppress(" UNCHECKED_CAST" )
198+ val addresses = getAddresses.invoke(client) as List <* >
199+ for (info in addresses) {
200+ if (info == null ) continue
201+ val hostname = getHostname.invoke(info) as ? String
202+ if (! hostname.isNullOrEmpty()) {
203+ hostnameByMAC[mac] = hostname
204+ }
205+ }
206+ } catch (e: Exception ) {
207+ Log .e(" RootServer" , " handleClientsChanged reflection error" , e)
208+ }
209+ }
210+ Log .d(" RootServer" , " tethered clients updated: ${hostnameByMAC.size} hostnames" )
211+ lastNeighborEntries?.let { broadcastEnrichedEntries(it) }
34212 }
35213
36214 override fun onBind (intent : Intent ): IBinder = binder
215+
216+ override fun onDestroy () {
217+ stopTetheringMonitor()
218+ neighborSubscription?.close()
219+ neighborSubscription = null
220+ neighborCallbacks.kill()
221+ super .onDestroy()
222+ }
37223}
0 commit comments