Support refresh routing rules

Useful when VPN interface has changed.
This commit is contained in:
Mygod
2018-01-05 10:07:20 +08:00
parent e82ba20c20
commit c37901a2de
7 changed files with 87 additions and 74 deletions

View File

@@ -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() {

View File

@@ -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

View File

@@ -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")

View File

@@ -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
}
}

View 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>

View File

@@ -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"

View File

@@ -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">