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.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
|
import java.net.InetAddress
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
||||||
@@ -67,6 +68,22 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
Status.ACTIVE_P2P -> removeGroup()
|
Status.ACTIVE_P2P -> removeGroup()
|
||||||
else -> clean()
|
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 }
|
private val wifiManager by lazy { getSystemService(Context.WIFI_SERVICE) as WifiManager }
|
||||||
@@ -164,26 +181,34 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
unregisterReceiver()
|
unregisterReceiver()
|
||||||
registerReceiver(receiver, intentFilter(WIFI_AP_STATE_CHANGED_ACTION))
|
registerReceiver(receiver, intentFilter(WIFI_AP_STATE_CHANGED_ACTION))
|
||||||
receiverRegistered = true
|
receiverRegistered = true
|
||||||
val routing = try {
|
try {
|
||||||
Routing(upstream, wifi)
|
if (initApRouting()) {
|
||||||
} 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
|
|
||||||
apConfiguration = NetUtils.loadApConfiguration()
|
apConfiguration = NetUtils.loadApConfiguration()
|
||||||
status = Status.ACTIVE_AP
|
status = Status.ACTIVE_AP
|
||||||
showNotification()
|
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")
|
else -> startFailure("Wi-Fi direct unavailable and hotspot disabled, please enable either")
|
||||||
}
|
}
|
||||||
return START_NOT_STICKY
|
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) {
|
private fun startFailure(msg: String?, group: WifiP2pGroup? = null) {
|
||||||
Toast.makeText(this@HotspotService, msg, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
|
||||||
showNotification()
|
showNotification()
|
||||||
if (group != null) removeGroup() else clean()
|
if (group != null) removeGroup() else clean()
|
||||||
}
|
}
|
||||||
@@ -224,17 +249,26 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
val downstream = group.`interface`
|
val downstream = group.`interface`
|
||||||
if (!info.groupFormed || !info.isGroupOwner || downstream == null || owner == null) return
|
if (!info.groupFormed || !info.isGroupOwner || downstream == null || owner == null) return
|
||||||
receiverRegistered = true
|
receiverRegistered = true
|
||||||
val routing = try {
|
try {
|
||||||
Routing(upstream, downstream, owner)
|
if (initP2pRouting(downstream, owner)) doStart(group)
|
||||||
} catch (_: Routing.InterfaceNotFoundException) {
|
else startFailure("Something went wrong, please check logcat.", group)
|
||||||
startFailure(getString(R.string.exception_interface_not_found), group)
|
} catch (e: Routing.InterfaceNotFoundException) {
|
||||||
|
startFailure(e.message, group)
|
||||||
return
|
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)
|
.rule().forward().dnsRedirect(dns)
|
||||||
if (routing.start()) {
|
return if (routing.start()) {
|
||||||
this.routing = routing
|
this.routing = routing
|
||||||
doStart(group)
|
true
|
||||||
} else startFailure("Something went wrong, please check logcat.", group)
|
} else {
|
||||||
|
routing.stop()
|
||||||
|
this.routing = null
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeGroup() {
|
private fun removeGroup() {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class MainActivity : AppCompatActivity(), ServiceConnection, Toolbar.OnMenuItemC
|
|||||||
}
|
}
|
||||||
holder.binding.device = device
|
holder.binding.device = device
|
||||||
holder.binding.ipAddress = when (position) {
|
holder.binding.ipAddress = when (position) {
|
||||||
0 -> binder?.service?.routing?.hostAddress
|
0 -> binder?.service?.routing?.hostAddress?.hostAddress
|
||||||
else -> arpCache[device?.deviceAddress]
|
else -> arpCache[device?.deviceAddress]
|
||||||
}
|
}
|
||||||
holder.binding.executePendingBindings()
|
holder.binding.executePendingBindings()
|
||||||
@@ -116,6 +116,14 @@ class MainActivity : AppCompatActivity(), ServiceConnection, Toolbar.OnMenuItemC
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
|
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 -> {
|
R.id.settings -> {
|
||||||
startActivity(Intent(this, SettingsActivity::class.java))
|
startActivity(Intent(this, SettingsActivity::class.java))
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package be.mygod.vpnhotspot
|
package be.mygod.vpnhotspot
|
||||||
|
|
||||||
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.Inet4Address
|
import java.net.Inet4Address
|
||||||
import java.net.InetAddress
|
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")
|
"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()
|
val hostAddress = ownerAddress ?: NetworkInterface.getByName(downstream)?.inetAddresses?.asSequence()
|
||||||
?.singleOrNull { it is Inet4Address } ?: throw InterfaceNotFoundException()).hostAddress
|
?.singleOrNull { it is Inet4Address } ?: throw InterfaceNotFoundException()
|
||||||
private val startScript = LinkedList<String>()
|
private val startScript = LinkedList<String>()
|
||||||
private val stopScript = 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 {
|
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 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")
|
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")
|
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
|
package be.mygod.vpnhotspot
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
|
||||||
import android.support.customtabs.CustomTabsIntent
|
import android.support.customtabs.CustomTabsIntent
|
||||||
import android.support.v4.content.ContextCompat
|
import android.support.v4.content.ContextCompat
|
||||||
import android.support.v4.content.LocalBroadcastManager
|
|
||||||
import android.support.v7.preference.Preference
|
import android.support.v7.preference.Preference
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreferenceDialogFragmentCompat
|
import be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreferenceDialogFragmentCompat
|
||||||
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers
|
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompatDividers(), ServiceConnection {
|
class SettingsFragment : PreferenceFragmentCompatDividers() {
|
||||||
private lateinit var service: Preference
|
|
||||||
private var binder: HotspotService.HotspotBinder? = null
|
|
||||||
private val statusListener = broadcastReceiver { _, _ -> onStatusChanged() }
|
|
||||||
private val customTabsIntent by lazy {
|
private val customTabsIntent by lazy {
|
||||||
CustomTabsIntent.Builder().setToolbarColor(ContextCompat.getColor(activity!!, R.color.colorPrimary)).build()
|
CustomTabsIntent.Builder().setToolbarColor(ContextCompat.getColor(activity!!, R.color.colorPrimary)).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
addPreferencesFromResource(R.xml.pref_settings)
|
addPreferencesFromResource(R.xml.pref_settings)
|
||||||
service = findPreference("service")
|
|
||||||
findPreference("service.clean").setOnPreferenceClickListener {
|
|
||||||
Routing.clean()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
findPreference("misc.logcat").setOnPreferenceClickListener {
|
findPreference("misc.logcat").setOnPreferenceClickListener {
|
||||||
val intent = Intent(Intent.ACTION_SEND)
|
val intent = Intent(Intent.ACTION_SEND)
|
||||||
.setType("text/plain")
|
.setType("text/plain")
|
||||||
@@ -61,33 +48,4 @@ class SettingsFragment : PreferenceFragmentCompatDividers(), ServiceConnection {
|
|||||||
.put(AlwaysAutoCompleteEditTextPreferenceDialogFragmentCompat.KEY_SUGGESTIONS, app.wifiInterfaces))
|
.put(AlwaysAutoCompleteEditTextPreferenceDialogFragmentCompat.KEY_SUGGESTIONS, app.wifiInterfaces))
|
||||||
else -> super.onDisplayPreferenceDialog(preference)
|
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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
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
|
<item
|
||||||
android:id="@+id/settings"
|
android:id="@+id/settings"
|
||||||
android:icon="@drawable/ic_action_settings"
|
android:icon="@drawable/ic_action_settings"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="service"
|
|
||||||
android:title="Service">
|
android:title="Service">
|
||||||
<be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreference
|
<be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreference
|
||||||
android:key="service.upstream"
|
android:key="service.upstream"
|
||||||
@@ -18,10 +17,6 @@
|
|||||||
android:title="Downstream DNS server:port"
|
android:title="Downstream DNS server:port"
|
||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
android:defaultValue="8.8.8.8:53"/>
|
android:defaultValue="8.8.8.8:53"/>
|
||||||
<Preference
|
|
||||||
android:key="service.clean"
|
|
||||||
android:title="Clean up"
|
|
||||||
android:summary="Remove routing rules"/>
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="Misc">
|
android:title="Misc">
|
||||||
|
|||||||
Reference in New Issue
Block a user