Support refresh routing rules
Useful when VPN interface has changed.
This commit is contained in:
@@ -18,6 +18,7 @@ import android.support.v4.content.LocalBroadcastManager
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import java.net.InetAddress
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
||||
@@ -67,6 +68,22 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
||||
Status.ACTIVE_P2P -> removeGroup()
|
||||
else -> clean()
|
||||
}
|
||||
|
||||
fun reapplyRouting() {
|
||||
val routing = routing
|
||||
routing?.stop()
|
||||
try {
|
||||
if (!when (status) {
|
||||
Status.ACTIVE_P2P -> initP2pRouting(routing!!.downstream, routing.hostAddress)
|
||||
Status.ACTIVE_AP -> initApRouting(routing!!.hostAddress)
|
||||
else -> false
|
||||
}) Toast.makeText(this@HotspotService, "Something went wrong, please check logcat.",
|
||||
Toast.LENGTH_SHORT).show()
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(this@HotspotService, e.message, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val wifiManager by lazy { getSystemService(Context.WIFI_SERVICE) as WifiManager }
|
||||
@@ -164,26 +181,34 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
||||
unregisterReceiver()
|
||||
registerReceiver(receiver, intentFilter(WIFI_AP_STATE_CHANGED_ACTION))
|
||||
receiverRegistered = true
|
||||
val routing = try {
|
||||
Routing(upstream, wifi)
|
||||
} catch (_: Routing.InterfaceNotFoundException) {
|
||||
startFailure(getString(R.string.exception_interface_not_found))
|
||||
return START_NOT_STICKY
|
||||
}.rule().forward().dnsRedirect(dns)
|
||||
if (routing.start()) {
|
||||
this.routing = routing
|
||||
try {
|
||||
if (initApRouting()) {
|
||||
apConfiguration = NetUtils.loadApConfiguration()
|
||||
status = Status.ACTIVE_AP
|
||||
showNotification()
|
||||
} else startFailure("Something went wrong, please check logcat.")
|
||||
} else startFailure("Something went wrong, please check logcat.", group)
|
||||
} catch (e: Routing.InterfaceNotFoundException) {
|
||||
startFailure(e.message, group)
|
||||
}
|
||||
}
|
||||
else -> startFailure("Wi-Fi direct unavailable and hotspot disabled, please enable either")
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
private fun initApRouting(owner: InetAddress? = null): Boolean {
|
||||
val routing = Routing(upstream, wifi, owner).rule().forward().dnsRedirect(dns)
|
||||
return if (routing.start()) {
|
||||
this.routing = routing
|
||||
true
|
||||
} else {
|
||||
routing.stop()
|
||||
this.routing = null
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun startFailure(msg: String, group: WifiP2pGroup? = null) {
|
||||
Toast.makeText(this@HotspotService, msg, Toast.LENGTH_SHORT).show()
|
||||
private fun startFailure(msg: String?, group: WifiP2pGroup? = null) {
|
||||
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
|
||||
showNotification()
|
||||
if (group != null) removeGroup() else clean()
|
||||
}
|
||||
@@ -224,17 +249,26 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
||||
val downstream = group.`interface`
|
||||
if (!info.groupFormed || !info.isGroupOwner || downstream == null || owner == null) return
|
||||
receiverRegistered = true
|
||||
val routing = try {
|
||||
Routing(upstream, downstream, owner)
|
||||
} catch (_: Routing.InterfaceNotFoundException) {
|
||||
startFailure(getString(R.string.exception_interface_not_found), group)
|
||||
try {
|
||||
if (initP2pRouting(downstream, owner)) doStart(group)
|
||||
else startFailure("Something went wrong, please check logcat.", group)
|
||||
} catch (e: Routing.InterfaceNotFoundException) {
|
||||
startFailure(e.message, group)
|
||||
return
|
||||
}.ipForward() // Wi-Fi direct doesn't enable ip_forward
|
||||
}
|
||||
}
|
||||
private fun initP2pRouting(downstream: String, owner: InetAddress): Boolean {
|
||||
val routing = Routing(upstream, downstream, owner)
|
||||
.ipForward() // Wi-Fi direct doesn't enable ip_forward
|
||||
.rule().forward().dnsRedirect(dns)
|
||||
if (routing.start()) {
|
||||
return if (routing.start()) {
|
||||
this.routing = routing
|
||||
doStart(group)
|
||||
} else startFailure("Something went wrong, please check logcat.", group)
|
||||
true
|
||||
} else {
|
||||
routing.stop()
|
||||
this.routing = null
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeGroup() {
|
||||
|
||||
@@ -87,7 +87,7 @@ class MainActivity : AppCompatActivity(), ServiceConnection, Toolbar.OnMenuItemC
|
||||
}
|
||||
holder.binding.device = device
|
||||
holder.binding.ipAddress = when (position) {
|
||||
0 -> binder?.service?.routing?.hostAddress
|
||||
0 -> binder?.service?.routing?.hostAddress?.hostAddress
|
||||
else -> arpCache[device?.deviceAddress]
|
||||
}
|
||||
holder.binding.executePendingBindings()
|
||||
@@ -116,6 +116,14 @@ class MainActivity : AppCompatActivity(), ServiceConnection, Toolbar.OnMenuItemC
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
|
||||
R.id.reapply -> {
|
||||
val binder = binder
|
||||
when (binder?.service?.status) {
|
||||
HotspotService.Status.IDLE -> Routing.clean()
|
||||
HotspotService.Status.ACTIVE_P2P, HotspotService.Status.ACTIVE_AP -> binder.reapplyRouting()
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.settings -> {
|
||||
startActivity(Intent(this, SettingsActivity::class.java))
|
||||
true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package be.mygod.vpnhotspot
|
||||
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import java.io.IOException
|
||||
import java.net.Inet4Address
|
||||
import java.net.InetAddress
|
||||
@@ -16,10 +17,12 @@ class Routing(private val upstream: String, val downstream: String, ownerAddress
|
||||
"while ip rule del priority 17999; do done")
|
||||
}
|
||||
|
||||
class InterfaceNotFoundException : IOException()
|
||||
class InterfaceNotFoundException : IOException() {
|
||||
override val message: String get() = app.getString(R.string.exception_interface_not_found)
|
||||
}
|
||||
|
||||
val hostAddress: String = (ownerAddress ?: NetworkInterface.getByName(downstream)?.inetAddresses?.asSequence()
|
||||
?.singleOrNull { it is Inet4Address } ?: throw InterfaceNotFoundException()).hostAddress
|
||||
val hostAddress = ownerAddress ?: NetworkInterface.getByName(downstream)?.inetAddresses?.asSequence()
|
||||
?.singleOrNull { it is Inet4Address } ?: throw InterfaceNotFoundException()
|
||||
private val startScript = LinkedList<String>()
|
||||
private val stopScript = LinkedList<String>()
|
||||
|
||||
@@ -52,6 +55,7 @@ class Routing(private val upstream: String, val downstream: String, ownerAddress
|
||||
}
|
||||
|
||||
fun dnsRedirect(dns: String): Routing {
|
||||
val hostAddress = hostAddress.hostAddress
|
||||
startScript.add("iptables -t nat -A PREROUTING -i $downstream -p tcp -d $hostAddress --dport 53 -j DNAT --to-destination $dns")
|
||||
startScript.add("iptables -t nat -A PREROUTING -i $downstream -p udp -d $hostAddress --dport 53 -j DNAT --to-destination $dns")
|
||||
stopScript.addFirst("iptables -t nat -D PREROUTING -i $downstream -p tcp -d $hostAddress --dport 53 -j DNAT --to-destination $dns")
|
||||
|
||||
@@ -1,36 +1,23 @@
|
||||
package be.mygod.vpnhotspot
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.support.customtabs.CustomTabsIntent
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v4.content.LocalBroadcastManager
|
||||
import android.support.v7.preference.Preference
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreferenceDialogFragmentCompat
|
||||
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers
|
||||
import java.net.NetworkInterface
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompatDividers(), ServiceConnection {
|
||||
private lateinit var service: Preference
|
||||
private var binder: HotspotService.HotspotBinder? = null
|
||||
private val statusListener = broadcastReceiver { _, _ -> onStatusChanged() }
|
||||
class SettingsFragment : PreferenceFragmentCompatDividers() {
|
||||
private val customTabsIntent by lazy {
|
||||
CustomTabsIntent.Builder().setToolbarColor(ContextCompat.getColor(activity!!, R.color.colorPrimary)).build()
|
||||
}
|
||||
|
||||
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_settings)
|
||||
service = findPreference("service")
|
||||
findPreference("service.clean").setOnPreferenceClickListener {
|
||||
Routing.clean()
|
||||
true
|
||||
}
|
||||
findPreference("misc.logcat").setOnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
.setType("text/plain")
|
||||
@@ -61,33 +48,4 @@ class SettingsFragment : PreferenceFragmentCompatDividers(), ServiceConnection {
|
||||
.put(AlwaysAutoCompleteEditTextPreferenceDialogFragmentCompat.KEY_SUGGESTIONS, app.wifiInterfaces))
|
||||
else -> super.onDisplayPreferenceDialog(preference)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
val activity = activity!!
|
||||
activity.bindService(Intent(activity, HotspotService::class.java), this, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
onServiceDisconnected(null)
|
||||
activity!!.unbindService(this)
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
binder = service as HotspotService.HotspotBinder
|
||||
onStatusChanged()
|
||||
LocalBroadcastManager.getInstance(activity!!)
|
||||
.registerReceiver(statusListener, intentFilter(HotspotService.STATUS_CHANGED))
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
LocalBroadcastManager.getInstance(activity!!).unregisterReceiver(statusListener)
|
||||
binder = null
|
||||
service.isEnabled = false
|
||||
}
|
||||
|
||||
private fun onStatusChanged() {
|
||||
service.isEnabled = binder!!.service.status == HotspotService.Status.IDLE
|
||||
}
|
||||
}
|
||||
|
||||
9
mobile/src/main/res/drawable/ic_navigation_refresh.xml
Normal file
9
mobile/src/main/res/drawable/ic_navigation_refresh.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
|
||||
</vector>
|
||||
@@ -1,6 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/reapply"
|
||||
android:icon="@drawable/ic_navigation_refresh"
|
||||
android:title="Reapply routing rules"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/settings"
|
||||
android:icon="@drawable/ic_action_settings"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceCategory
|
||||
android:key="service"
|
||||
android:title="Service">
|
||||
<be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreference
|
||||
android:key="service.upstream"
|
||||
@@ -18,10 +17,6 @@
|
||||
android:title="Downstream DNS server:port"
|
||||
android:summary="%s"
|
||||
android:defaultValue="8.8.8.8:53"/>
|
||||
<Preference
|
||||
android:key="service.clean"
|
||||
android:title="Clean up"
|
||||
android:summary="Remove routing rules"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="Misc">
|
||||
|
||||
Reference in New Issue
Block a user