From c37901a2de9a079ffd060f093c93fb2f3f42cae4 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 5 Jan 2018 10:07:20 +0800 Subject: [PATCH] Support refresh routing rules Useful when VPN interface has changed. --- .../be/mygod/vpnhotspot/HotspotService.kt | 78 +++++++++++++------ .../java/be/mygod/vpnhotspot/MainActivity.kt | 10 ++- .../main/java/be/mygod/vpnhotspot/Routing.kt | 10 ++- .../be/mygod/vpnhotspot/SettingsFragment.kt | 44 +---------- .../res/drawable/ic_navigation_refresh.xml | 9 +++ mobile/src/main/res/menu/main.xml | 5 ++ mobile/src/main/res/xml/pref_settings.xml | 5 -- 7 files changed, 87 insertions(+), 74 deletions(-) create mode 100644 mobile/src/main/res/drawable/ic_navigation_refresh.xml diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt index 1a703450..71e17914 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt @@ -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 - apConfiguration = NetUtils.loadApConfiguration() - status = Status.ACTIVE_AP - showNotification() - } else startFailure("Something went wrong, please check logcat.") + try { + if (initApRouting()) { + apConfiguration = NetUtils.loadApConfiguration() + status = Status.ACTIVE_AP + showNotification() + } 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() { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt b/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt index d36a0a07..a647a373 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt @@ -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 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/Routing.kt b/mobile/src/main/java/be/mygod/vpnhotspot/Routing.kt index ae47be3d..d32e3178 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/Routing.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/Routing.kt @@ -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() private val stopScript = LinkedList() @@ -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") diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsFragment.kt index cd5fa1ea..0a7b15e3 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsFragment.kt @@ -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 - } } diff --git a/mobile/src/main/res/drawable/ic_navigation_refresh.xml b/mobile/src/main/res/drawable/ic_navigation_refresh.xml new file mode 100644 index 00000000..e93847ee --- /dev/null +++ b/mobile/src/main/res/drawable/ic_navigation_refresh.xml @@ -0,0 +1,9 @@ + + + diff --git a/mobile/src/main/res/menu/main.xml b/mobile/src/main/res/menu/main.xml index 279445c5..09f9416b 100644 --- a/mobile/src/main/res/menu/main.xml +++ b/mobile/src/main/res/menu/main.xml @@ -1,6 +1,11 @@ + -