Implement connected devices (buggy debug version)

This commit is contained in:
Mygod
2018-01-03 17:57:25 +08:00
parent b073109d4f
commit e486c8fcf6
6 changed files with 192 additions and 78 deletions

2
.idea/misc.xml generated
View File

@@ -93,7 +93,7 @@
</value> </value>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -5,8 +5,8 @@ import android.app.Service
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.wifi.p2p.WifiP2pGroup import android.net.NetworkInfo
import android.net.wifi.p2p.WifiP2pManager import android.net.wifi.p2p.*
import android.os.Binder import android.os.Binder
import android.os.Looper import android.os.Looper
import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat
@@ -45,18 +45,61 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
private lateinit var p2pManager: WifiP2pManager private lateinit var p2pManager: WifiP2pManager
private lateinit var channel: WifiP2pManager.Channel private lateinit var channel: WifiP2pManager.Channel
private lateinit var group: WifiP2pGroup lateinit var group: WifiP2pGroup
private var receiver: BroadcastReceiver? = null lateinit var clients: MutableCollection<WifiP2pDevice>
private val binder = HotspotBinder() private val binder = HotspotBinder()
private var receiverRegistered = false
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION ->
if (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 0) ==
WifiP2pManager.WIFI_P2P_STATE_DISABLED) clean() // group may be enabled by other apps
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION ->{
clients = intent.getParcelableExtra<WifiP2pDeviceList>(WifiP2pManager.EXTRA_P2P_DEVICE_LIST).deviceList
binder.data?.onClientsChanged()
}
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
val info = intent.getParcelableExtra<WifiP2pInfo>(WifiP2pManager.EXTRA_WIFI_P2P_INFO)
val net = intent.getParcelableExtra<NetworkInfo>(WifiP2pManager.EXTRA_NETWORK_INFO)
val group = intent.getParcelableExtra<WifiP2pGroup>(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)
val downstream = group.`interface`
if (net.isConnected && downstream != null && this@HotspotService.downstream == null) {
this@HotspotService.downstream = downstream
if (noisySu("echo 1 >/proc/sys/net/ipv4/ip_forward",
"ip route add default dev $upstream scope link table 62",
"ip route add $route dev $downstream scope link table 62",
"ip route add broadcast 255.255.255.255 dev $downstream scope link table 62",
"ip rule add from $route lookup 62",
"iptables -N vpnhotspot_fwd",
"iptables -A vpnhotspot_fwd -i $upstream -o $downstream -m state --state ESTABLISHED,RELATED -j ACCEPT",
"iptables -A vpnhotspot_fwd -i $downstream -o $upstream -j ACCEPT",
"iptables -I FORWARD -j vpnhotspot_fwd",
"iptables -t nat -A PREROUTING -i $downstream -p tcp --dport 53 -j DNAT --to-destination $dns",
"iptables -t nat -A PREROUTING -i $downstream -p udp --dport 53 -j DNAT --to-destination $dns")) {
doStart(group)
} else startFailure("Something went wrong, please check logcat.")
}
group.`interface`
binder.data?.onGroupChanged()
Log.d(TAG, "${intent.action}: $info, $net, $group")
}
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
val info = intent.getParcelableExtra<WifiP2pInfo>(WifiP2pManager.EXTRA_WIFI_P2P_INFO)
Log.d(TAG, "${intent.action}: $info")
}
}
}
}
// TODO: do something // TODO: do something to these hardcoded strings
private var downstream: String? = null
private val upstream = "tun0" private val upstream = "tun0"
private val downstream = "p2p0"
private val route = "192.168.49.0/24" private val route = "192.168.49.0/24"
private val dns = "8.8.8.8:53" private val dns = "8.8.8.8:53"
private var status = Status.IDLE var status = Status.IDLE
set(value) { private set(value) {
field = value field = value
binder.data?.onStatusChanged() binder.data?.onStatusChanged()
} }
@@ -76,20 +119,13 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (status != Status.IDLE) return START_NOT_STICKY if (status != Status.IDLE) return START_NOT_STICKY
status = Status.STARTING status = Status.STARTING
initReceiver() if (!receiverRegistered) {
if (!noisySu("echo 1 >/proc/sys/net/ipv4/ip_forward", registerReceiver(receiver, createIntentFilter(
"ip route add default dev $upstream scope link table 62", WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
"ip route add $route dev $downstream scope link table 62", WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION,
"ip route add broadcast 255.255.255.255 dev $downstream scope link table 62", WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION,
"ip rule add from $route lookup 62", WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION))
"iptables -N vpnhotspot_fwd", receiverRegistered = true
"iptables -A vpnhotspot_fwd -i $upstream -o $downstream -m state --state ESTABLISHED,RELATED -j ACCEPT",
"iptables -A vpnhotspot_fwd -i $downstream -o $upstream -j ACCEPT",
"iptables -I FORWARD -j vpnhotspot_fwd",
"iptables -t nat -A PREROUTING -i $downstream -p tcp --dport 53 -j DNAT --to-destination $dns",
"iptables -t nat -A PREROUTING -i $downstream -p udp --dport 53 -j DNAT --to-destination $dns")) {
startFailure("Something went wrong, please check logcat.")
return START_NOT_STICKY
} }
p2pManager.requestGroupInfo(channel, { p2pManager.requestGroupInfo(channel, {
when { when {
@@ -115,70 +151,48 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
startForeground(0, NotificationCompat.Builder(this@HotspotService, CHANNEL).build()) startForeground(0, NotificationCompat.Builder(this@HotspotService, CHANNEL).build())
clean() clean()
} }
private fun doStart() { private fun doStart() = p2pManager.createGroup(channel, object : WifiP2pManager.ActionListener {
p2pManager.createGroup(channel, object : WifiP2pManager.ActionListener, WifiP2pManager.GroupInfoListener { override fun onFailure(reason: Int) = startFailure("Failed to create P2P group (reason: $reason)")
override fun onFailure(reason: Int) = startFailure("Failed to create P2P group (reason: $reason)") override fun onSuccess() { } // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire
})
private var tries = 0
override fun onSuccess() = p2pManager.requestGroupInfo(channel, this)
override fun onGroupInfoAvailable(group: WifiP2pGroup?) {
if (group != null && group.isGroupOwner) doStart(group) else if (tries < 10) {
Thread.sleep(30L shl tries++)
onSuccess()
} else startFailure("Unexpected group: $group")
}
})
}
private fun doStart(group: WifiP2pGroup) { private fun doStart(group: WifiP2pGroup) {
status = Status.ACTIVE
this.group = group this.group = group
clients = group.clientList
status = Status.ACTIVE
startForeground(1, NotificationCompat.Builder(this@HotspotService, CHANNEL) startForeground(1, NotificationCompat.Builder(this@HotspotService, CHANNEL)
.setWhen(0)
.setColor(ContextCompat.getColor(this@HotspotService, R.color.colorPrimary)) .setColor(ContextCompat.getColor(this@HotspotService, R.color.colorPrimary))
.setContentTitle(group.networkName) .setContentTitle(group.networkName)
.setSubText(group.passphrase) .setContentText(group.passphrase)
.setSmallIcon(R.drawable.ic_device_wifi_tethering) .setSmallIcon(R.drawable.ic_device_wifi_tethering)
.setContentIntent(PendingIntent.getActivity(this, 0, .setContentIntent(PendingIntent.getActivity(this, 0,
Intent(this, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT)) Intent(this, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
.build()) .build())
} }
private fun initReceiver() {
return
receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
// TODO
}
}
}
registerReceiver(receiver, createIntentFilter(
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION,
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION,
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION))
}
private fun clean() { private fun clean() {
if (!noisySu("iptables -t nat -D PREROUTING -i $downstream -p tcp --dport 53 -j DNAT --to-destination $dns", if (receiverRegistered) {
"iptables -t nat -D PREROUTING -i $downstream -p udp --dport 53 -j DNAT --to-destination $dns", unregisterReceiver(receiver)
"iptables -D FORWARD -j vpnhotspot_fwd", receiverRegistered = false
"iptables -F vpnhotspot_fwd", }
"iptables -X vpnhotspot_fwd", if (downstream != null)
"ip rule del from $route lookup 62", if (noisySu("iptables -t nat -D PREROUTING -i $downstream -p tcp --dport 53 -j DNAT --to-destination $dns",
"ip route del broadcast 255.255.255.255 dev $downstream scope link table 62", "iptables -t nat -D PREROUTING -i $downstream -p udp --dport 53 -j DNAT --to-destination $dns",
"ip route del $route dev $downstream scope link table 62", "iptables -D FORWARD -j vpnhotspot_fwd",
"ip route del default dev $upstream scope link table 62")) "iptables -F vpnhotspot_fwd",
Toast.makeText(this, "Something went wrong, please check logcat.", Toast.LENGTH_SHORT).show() "iptables -X vpnhotspot_fwd",
"ip rule del from $route lookup 62",
"ip route del broadcast 255.255.255.255 dev $downstream scope link table 62",
"ip route del $route dev $downstream scope link table 62",
"ip route del default dev $upstream scope link table 62")) {
downstream = null
} else Toast.makeText(this, "Something went wrong, please check logcat.", Toast.LENGTH_SHORT).show()
status = Status.IDLE status = Status.IDLE
stopForeground(true) stopForeground(true)
} }
override fun onDestroy() { override fun onDestroy() {
if (status != Status.IDLE) binder.shutdown() if (status != Status.IDLE) binder.shutdown()
if (receiver != null) {
unregisterReceiver(receiver)
receiver = null
}
super.onDestroy() super.onDestroy()
} }
} }

View File

@@ -7,10 +7,17 @@ import android.content.ServiceConnection
import android.databinding.BaseObservable import android.databinding.BaseObservable
import android.databinding.Bindable import android.databinding.Bindable
import android.databinding.DataBindingUtil import android.databinding.DataBindingUtil
import android.net.wifi.p2p.WifiP2pDevice
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import be.mygod.vpnhotspot.databinding.ClientBinding
import be.mygod.vpnhotspot.databinding.MainActivityBinding import be.mygod.vpnhotspot.databinding.MainActivityBinding
class MainActivity : AppCompatActivity(), ServiceConnection { class MainActivity : AppCompatActivity(), ServiceConnection {
@@ -37,23 +44,66 @@ class MainActivity : AppCompatActivity(), ServiceConnection {
} }
} }
val running get() = binder?.status == HotspotService.Status.ACTIVE
val ssid: String @Bindable get() = if (running) binder!!.service.group.networkName else ""
val password: String @Bindable get() = if (running) binder!!.service.group.passphrase else ""
val clients @Bindable get() = if (running) binder!!.service.clients else null
fun onStatusChanged() { fun onStatusChanged() {
notifyPropertyChanged(BR.switchEnabled) notifyPropertyChanged(BR.switchEnabled)
notifyPropertyChanged(BR.serviceStarted) notifyPropertyChanged(BR.serviceStarted)
onGroupChanged()
onClientsChanged()
} }
fun onBinderChanged() { fun onGroupChanged() {
onStatusChanged() notifyPropertyChanged(BR.ssid)
notifyPropertyChanged(BR.password)
} }
fun onClientsChanged() = adapter.fetchClients()
}
class ClientViewHolder(val binding: ClientBinding) : RecyclerView.ViewHolder(binding.root)
inner class ClientAdapter : RecyclerView.Adapter<ClientViewHolder>() {
private var owner: WifiP2pDevice? = null
private lateinit var clients: MutableCollection<WifiP2pDevice>
fun fetchClients() {
val binder = binder!!
if (data.running) {
owner = binder.service.group.owner
clients = binder.service.clients
} else owner = null
notifyDataSetChanged() // recreate everything
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
ClientViewHolder(ClientBinding.inflate(LayoutInflater.from(parent.context)))
override fun onBindViewHolder(holder: ClientViewHolder, position: Int) {
holder.binding.device = when (position) {
0 -> owner
else -> clients.elementAt(position - 1)
}
holder.binding.executePendingBindings()
}
override fun getItemCount() = if (owner == null) 0 else 1 + clients.size
} }
private lateinit var binding: MainActivityBinding private lateinit var binding: MainActivityBinding
private val data = Data() private val data = Data()
private val adapter = ClientAdapter()
private var binder: HotspotService.HotspotBinder? = null private var binder: HotspotService.HotspotBinder? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.main_activity) binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
binding.data = data binding.data = data
binding.clients.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
val animator = DefaultItemAnimator()
animator.supportsChangeAnimations = false // prevent fading-in/out when rebinding
binding.clients.itemAnimator = animator
binding.clients.adapter = adapter
} }
override fun onStart() { override fun onStart() {
@@ -70,11 +120,12 @@ class MainActivity : AppCompatActivity(), ServiceConnection {
val binder = service as HotspotService.HotspotBinder val binder = service as HotspotService.HotspotBinder
binder.data = data binder.data = data
this.binder = binder this.binder = binder
data.onBinderChanged() data.onStatusChanged()
} }
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
binder?.data = null binder?.data = null
binder = null binder = null
data.onStatusChanged()
} }
} }

View File

@@ -21,8 +21,8 @@ echo $NOISYSU_SUFFIX""")
var out = process.inputStream.bufferedReader().use { it.readLine() } var out = process.inputStream.bufferedReader().use { it.readLine() }
val result = out != NOISYSU_SUFFIX val result = out != NOISYSU_SUFFIX
out = out.removeSuffix(NOISYSU_SUFFIX) out = out.removeSuffix(NOISYSU_SUFFIX)
if (!out.isNullOrBlank()) Log.i("noisySu", out) if (!out.isNullOrBlank()) Log.i(NOISYSU_TAG, out)
val err = process.errorStream.bufferedReader().use { it.readLine() } val err = process.errorStream.bufferedReader().use { it.readLine() }
if (!err.isNullOrBlank()) Log.e("noisySu", err) if (!err.isNullOrBlank()) Log.e(NOISYSU_TAG, err)
return result return result
} }

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="device"
type="android.net.wifi.p2p.WifiP2pDevice"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{device.deviceName}"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
tools:text="Device name"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{device.deviceAddress}"
tools:text="xx:xx:xx:xx:xx:xx"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{device.primaryDeviceType}"
tools:text="Primary type"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{device.secondaryDeviceType}"
tools:text="Secondary type"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{Integer.toString(device.status)}"
tools:text="Status"/>
</LinearLayout>
</layout>

View File

@@ -22,7 +22,6 @@
android:id="@+id/toolbar"> android:id="@+id/toolbar">
<Switch <Switch
android:id="@+id/switchService"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
@@ -54,11 +53,11 @@
android:layout_row="0"/> android:layout_row="0"/>
<TextView <TextView
android:id="@+id/textSsid"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_column="2" android:layout_column="2"
android:layout_row="0" android:layout_row="0"
android:text="@{data.ssid}"
tools:text="DIRECT-rAnd0m"/> tools:text="DIRECT-rAnd0m"/>
<TextView <TextView
@@ -70,11 +69,11 @@
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/> android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
<TextView <TextView
android:id="@+id/textPassword"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_column="2" android:layout_column="2"
android:layout_row="1" android:layout_row="1"
android:text="@{data.password}"
tools:text="p4ssW0rd"/> tools:text="p4ssW0rd"/>
</GridLayout> </GridLayout>
@@ -93,11 +92,15 @@
android:backgroundTint="?android:attr/textColorSecondary"/> android:backgroundTint="?android:attr/textColorSecondary"/>
<android.support.v7.widget.RecyclerView <android.support.v7.widget.RecyclerView
android:id="@+id/clients"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingTop="8dp"/> android:paddingTop="8dp"
android:clipToPadding="false"
android:scrollbars="vertical"
tools:listitem="@layout/client"/>
</LinearLayout> </LinearLayout>
</layout> </layout>