Implement USE_EXIT_NODE intent
Closes tailscale/tailscale#8143. I map friendly labels from intent extras to tailscale node IDs, with empty string or not specifying the exitNode intent extra as the "no exit node" action. When an error is encountered, we will push a notification with a friendly message to the status notification channel. The tasker syntax I tested with locally is: Action: `com.tailscale.ipn.USE_EXIT_NODE` Package: `com.tailscale.ipn` Class: `com.tailscale.ipn.IPNReceiver` Target: Broadcast Receiver Extra: `exitNode:exitNodeLabelOrEmpty` Extra: `allowLanAccess:trueOrFalse` Signed-off-by: Fredric Silberberg <fred@silberberg.xyz>pull/142/head
parent
e6f6d35a99
commit
3f3d044276
@ -0,0 +1,90 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package com.tailscale.ipn
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.WorkerParameters
|
||||
import com.tailscale.ipn.App.Companion.STATUS_CHANNEL_ID
|
||||
import com.tailscale.ipn.App.Companion.STATUS_EXIT_NODE_FAILURE_NOTIFICATION_ID
|
||||
import com.tailscale.ipn.ui.localapi.Client
|
||||
import com.tailscale.ipn.ui.model.Ipn
|
||||
import com.tailscale.ipn.ui.notifier.Notifier
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
class UseExitNodeWorker(
|
||||
appContext: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : CoroutineWorker(appContext, workerParams) {
|
||||
override suspend fun doWork(): Result {
|
||||
suspend fun runAndGetResult(): String? {
|
||||
val exitNodeName = inputData.getString(EXIT_NODE_NAME)
|
||||
|
||||
val exitNodeId = if (exitNodeName.isNullOrEmpty()) {
|
||||
null
|
||||
} else {
|
||||
val peers =
|
||||
(Notifier.netmap.value
|
||||
?: run { return@runAndGetResult "Tailscale is not setup" })
|
||||
.Peers ?: run { return@runAndGetResult "No peers found" }
|
||||
|
||||
val filteredPeers = peers.filter {
|
||||
it.displayName == exitNodeName
|
||||
}.toList()
|
||||
|
||||
if (filteredPeers.isEmpty()) {
|
||||
return "No peers with name $exitNodeName found"
|
||||
} else if (filteredPeers.size > 1) {
|
||||
return "Multiple peers with name $exitNodeName found"
|
||||
} else if (!filteredPeers[0].isExitNode) {
|
||||
return "Peer with name $exitNodeName is not an exit node"
|
||||
}
|
||||
|
||||
filteredPeers[0].StableID
|
||||
}
|
||||
|
||||
val allowLanAccess = inputData.getBoolean(ALLOW_LAN_ACCESS, false)
|
||||
val prefsOut = Ipn.MaskedPrefs()
|
||||
prefsOut.ExitNodeID = exitNodeId
|
||||
prefsOut.ExitNodeAllowLANAccess = allowLanAccess
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Default + Job())
|
||||
var result: String? = null
|
||||
Client(scope).editPrefs(prefsOut) {
|
||||
result = if (it.isFailure) {
|
||||
it.exceptionOrNull()?.message
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
scope.coroutineContext[Job]?.join()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
val result = runAndGetResult()
|
||||
|
||||
return if (result != null) {
|
||||
App.appInstance.notify(
|
||||
title = "Use Exit Node Intent Failed",
|
||||
message = result,
|
||||
STATUS_CHANNEL_ID,
|
||||
intent = null,
|
||||
STATUS_EXIT_NODE_FAILURE_NOTIFICATION_ID
|
||||
)
|
||||
Result.failure(Data.Builder().putString(ERROR_KEY, result).build())
|
||||
} else {
|
||||
Result.success()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXIT_NODE_NAME = "EXIT_NODE_NAME"
|
||||
const val ALLOW_LAN_ACCESS = "ALLOW_LAN_ACCESS"
|
||||
const val ERROR_KEY = "error"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue