@@ -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,174 @@ 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" ->
153+ proxyObject.javaClass.name + " @" +
154+ Integer .toHexString(System .identityHashCode(proxyObject))
155+ " onClientsChanged" -> {
156+ if (args != null ) {
157+ @Suppress(" UNCHECKED_CAST" )
158+ handleClientsChanged(args[0 ] as Collection <* >)
159+ }
160+ null
161+ }
162+ else -> null
163+ }
164+ }
165+ tetheringCallback = proxy
166+ registerMethod.invoke(manager, Executors .newSingleThreadExecutor(), proxy)
167+ Log .d(" RootServer" , " TetheringManager monitor started" )
168+ } catch (e: Exception ) {
169+ Log .e(" RootServer" , " startTetheringMonitor failed" , e)
170+ }
171+ }
172+
173+ private fun stopTetheringMonitor () {
174+ val manager = tetheringManager ? : return
175+ val callback = tetheringCallback ? : return
176+ try {
177+ val callbackClass =
178+ Class .forName(" android.net.TetheringManager\$ TetheringEventCallback" )
179+ val unregisterMethod = manager.javaClass.getMethod(
180+ " unregisterTetheringEventCallback" ,
181+ callbackClass,
182+ )
183+ unregisterMethod.invoke(manager, callback)
184+ } catch (e: Exception ) {
185+ Log .e(" RootServer" , " stopTetheringMonitor failed" , e)
186+ }
187+ tetheringCallback = null
188+ tetheringManager = null
189+ hostnameByMAC.clear()
190+ }
191+
192+ private fun handleClientsChanged (clients : Collection <* >) {
193+ hostnameByMAC.clear()
194+ for (client in clients) {
195+ if (client == null ) continue
196+ try {
197+ val mac = getMacAddress.invoke(client).toString().uppercase()
198+
199+ @Suppress(" UNCHECKED_CAST" )
200+ val addresses = getAddresses.invoke(client) as List <* >
201+ for (info in addresses) {
202+ if (info == null ) continue
203+ val hostname = getHostname.invoke(info) as ? String
204+ if (! hostname.isNullOrEmpty()) {
205+ hostnameByMAC[mac] = hostname
206+ }
207+ }
208+ } catch (e: Exception ) {
209+ Log .e(" RootServer" , " handleClientsChanged reflection error" , e)
210+ }
211+ }
212+ Log .d(" RootServer" , " tethered clients updated: ${hostnameByMAC.size} hostnames" )
213+ lastNeighborEntries?.let { broadcastEnrichedEntries(it) }
34214 }
35215
36216 override fun onBind (intent : Intent ): IBinder = binder
217+
218+ override fun onDestroy () {
219+ stopTetheringMonitor()
220+ neighborSubscription?.close()
221+ neighborSubscription = null
222+ neighborCallbacks.kill()
223+ super .onDestroy()
224+ }
37225}
0 commit comments