oxtoacart/reactive_exit_node_bak
Percy Wegmann 3 months ago
parent a1e67ff1e9
commit dfd43a4195
No known key found for this signature in database
GPG Key ID: 29D8CDEB4C13D48B

@ -8,7 +8,6 @@ import com.tailscale.ipn.ui.model.BugReportID
import com.tailscale.ipn.ui.model.Errors
import com.tailscale.ipn.ui.model.Ipn
import com.tailscale.ipn.ui.model.IpnLocal
import com.tailscale.ipn.ui.model.IpnState
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -44,7 +43,6 @@ private object Endpoint {
const val TAILFS_SERVER_ADDRESS = "tailfs/fileserver-address"
}
typealias StatusResponseHandler = (Result<IpnState.Status>) -> Unit
typealias BugReportIdHandler = (Result<BugReportID>) -> Unit
typealias PrefsHandler = (Result<Ipn.Prefs>) -> Unit
@ -53,10 +51,6 @@ typealias PrefsHandler = (Result<Ipn.Prefs>) -> Unit
* corresponding method on this Client.
*/
class Client(private val scope: CoroutineScope) {
fun status(responseHandler: StatusResponseHandler) {
get(Endpoint.STATUS, responseHandler = responseHandler)
}
fun bugReportId(responseHandler: BugReportIdHandler) {
post(Endpoint.BUG_REPORT, responseHandler = responseHandler)
}

@ -1,30 +0,0 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package com.tailscale.ipn.ui.model
import kotlinx.serialization.Serializable
class Dns {
@Serializable
data class HostEntry(val addr: Addr?, val hosts: List<String>?)
@Serializable
data class OSConfig(
val hosts: List<HostEntry>? = null,
val nameservers: List<Addr>? = null,
val searchDomains: List<String>? = null,
val matchDomains: List<String>? = null,
) {
val isEmpty: Boolean
get() = (hosts.isNullOrEmpty()) &&
(nameservers.isNullOrEmpty()) &&
(searchDomains.isNullOrEmpty()) &&
(matchDomains.isNullOrEmpty())
}
}
class DnsType {
@Serializable
data class Resolver(var Addr: String? = null, var BootstrapResolution: List<Addr>? = null)
}

@ -9,13 +9,9 @@ class Ipn {
// Represents the overall state of the Tailscale engine.
enum class State(val value: Int) {
NoState(0),
InUseOtherUser(1),
NeedsLogin(2),
NeedsMachineAuth(3),
Stopped(4),
Starting(5),
Running(6);
NoState(0), InUseOtherUser(1), NeedsLogin(2), NeedsMachineAuth(3), Stopped(4), Starting(5), Running(
6
);
companion object {
fun fromInt(value: Int): State {
@ -34,13 +30,13 @@ class Ipn {
val FilesWaiting: Empty.Message? = null,
val State: Int? = null,
var Prefs: Prefs? = null,
var NetMap: Netmap.NetworkMap? = null,
var NetMap: NetworkMap? = null,
var Engine: EngineStatus? = null,
var BrowseToURL: String? = null,
var BackendLogId: String? = null,
var LocalTCPPort: Int? = null,
var IncomingFiles: List<PartialFile>? = null,
var ClientVersion: Tailcfg.ClientVersion? = null,
// var ClientVersion: Tailcfg.ClientVersion? = null, // Currently unused
var TailFSShares: Map<String, String>? = null,
)
@ -133,7 +129,7 @@ class Ipn {
val RBytes: Long,
val WBytes: Long,
val NumLive: Int,
val LivePeers: Map<String, IpnState.PeerStatusLite>,
// val LivePeers: Map<String, IpnState.PeerStatusLite>,
)
@Serializable
@ -151,12 +147,9 @@ class Ipn {
class Persist {
@Serializable
data class Persist(
var PrivateMachineKey: String =
"privkey:0000000000000000000000000000000000000000000000000000000000000000",
var PrivateNodeKey: String =
"privkey:0000000000000000000000000000000000000000000000000000000000000000",
var OldPrivateNodeKey: String =
"privkey:0000000000000000000000000000000000000000000000000000000000000000",
var PrivateMachineKey: String = "privkey:0000000000000000000000000000000000000000000000000000000000000000",
var PrivateNodeKey: String = "privkey:0000000000000000000000000000000000000000000000000000000000000000",
var OldPrivateNodeKey: String = "privkey:0000000000000000000000000000000000000000000000000000000000000000",
var Provider: String = "",
)
}

@ -5,105 +5,6 @@ package com.tailscale.ipn.ui.model
import kotlinx.serialization.Serializable
class IpnState {
@Serializable
data class PeerStatusLite(
val RxBytes: Long,
val TxBytes: Long,
val LastHandshake: String,
val NodeKey: String,
)
@Serializable
data class PeerStatus(
val ID: StableNodeID,
val HostName: String,
val DNSName: String,
val TailscaleIPs: List<Addr>? = null,
val Tags: List<String>? = null,
val PrimaryRoutes: List<String>? = null,
val Addrs: List<String>? = null,
val Online: Boolean,
val ExitNode: Boolean,
val ExitNodeOption: Boolean,
val Active: Boolean,
val PeerAPIURL: List<String>? = null,
val Capabilities: List<String>? = null,
val SSH_HostKeys: List<String>? = null,
val ShareeNode: Boolean? = null,
val Expired: Boolean? = null,
val Location: Tailcfg.Location? = null,
) {
fun computedName(status: Status): String {
val name = DNSName
val suffix = status.CurrentTailnet?.MagicDNSSuffix
suffix ?: return name
if (!(name.endsWith("." + suffix + "."))) {
return name
}
return name.dropLast(suffix.count() + 2)
}
}
@Serializable
data class ExitNodeStatus(
val ID: StableNodeID,
val Online: Boolean,
val TailscaleIPs: List<Prefix>? = null,
)
@Serializable
data class TailnetStatus(
val Name: String,
val MagicDNSSuffix: String,
val MagicDNSEnabled: Boolean,
)
@Serializable
data class Status(
val Version: String,
val TUN: Boolean,
val BackendState: String,
val AuthURL: String,
val TailscaleIPs: List<Addr>? = null,
val Self: PeerStatus? = null,
val ExitNodeStatus: ExitNodeStatus? = null,
val Health: List<String>? = null,
val CurrentTailnet: TailnetStatus? = null,
val CertDomains: List<String>? = null,
val Peer: Map<String, PeerStatus>? = null,
val User: Map<String, Tailcfg.UserProfile>? = null,
val ClientVersion: Tailcfg.ClientVersion? = null,
)
@Serializable
data class NetworkLockStatus(
var Enabled: Boolean,
var PublicKey: String,
var NodeKey: String,
var NodeKeySigned: Boolean,
var FilteredPeers: List<TKAFilteredPeer>? = null,
var StateID: ULong? = null,
)
@Serializable
data class TKAFilteredPeer(
var Name: String,
var TailscaleIPs: List<Addr>,
var NodeKey: String,
)
@Serializable
data class PingResult(
var IP: Addr,
var Err: String,
var LatencySeconds: Double,
)
}
class IpnLocal {
@Serializable
data class LoginProfile(

@ -5,18 +5,17 @@ package com.tailscale.ipn.ui.model
import kotlinx.serialization.Serializable
class Netmap {
@Serializable
data class NetworkMap(
@Serializable
data class NetworkMap(
var SelfNode: Tailcfg.Node,
var NodeKey: KeyNodePublic,
// var NodeKey: KeyNodePublic, // Currently unused
var Peers: List<Tailcfg.Node>? = null,
var Expiry: Time,
var Domain: String,
// var Expiry: Time, // Currently unused
// var Domain: String, // Currently unused
var UserProfiles: Map<String, Tailcfg.UserProfile>,
var TKAEnabled: Boolean,
var DNS: Tailcfg.DNSConfig? = null
) {
// var TKAEnabled: Boolean, // Currently unused
// var DNS: Tailcfg.DNSConfig? = null // Currently unused
) {
// Keys are tailcfg.UserIDs thet get stringified
// Helpers
fun currentUserProfile(): Tailcfg.UserProfile? {
@ -32,7 +31,7 @@ class Netmap {
}
fun getPeer(id: StableNodeID): Tailcfg.Node? {
if(id == SelfNode.StableID) {
if (id == SelfNode.StableID) {
return SelfNode
}
return Peers?.find { it.StableID == id }
@ -43,13 +42,12 @@ class Netmap {
if (other !is NetworkMap) return false
return SelfNode == other.SelfNode &&
NodeKey == other.NodeKey &&
// NodeKey == other.NodeKey &&
Peers == other.Peers &&
Expiry == other.Expiry &&
// Expiry == other.Expiry &&
User() == other.User() &&
Domain == other.Domain &&
UserProfiles == other.UserProfiles &&
TKAEnabled == other.TKAEnabled
}
// Domain == other.Domain &&
UserProfiles == other.UserProfiles
// TKAEnabled == other.TKAEnabled
}
}

@ -6,15 +6,16 @@ package com.tailscale.ipn.ui.model
import kotlinx.serialization.Serializable
class Tailcfg {
@Serializable
data class ClientVersion(
var RunningLatest: Boolean? = null,
var LatestVersion: String? = null,
var UrgentSecurityUpdate: Boolean? = null,
var Notify: Boolean? = null,
var NotifyURL: String? = null,
var NotifyText: String? = null
)
// Currently unused
// @Serializable
// data class ClientVersion(
// var RunningLatest: Boolean? = null,
// var LatestVersion: String? = null,
// var UrgentSecurityUpdate: Boolean? = null,
// var Notify: Boolean? = null,
// var NotifyURL: String? = null,
// var NotifyText: String? = null
// )
@Serializable
data class UserProfile(
@ -30,55 +31,60 @@ class Tailcfg {
@Serializable
data class Hostinfo(
var IPNVersion: String? = null,
var FrontendLogID: String? = null,
var BackendLogID: String? = null,
// var IPNVersion: String? = null, // Currently unused
// var FrontendLogID: String? = null, // Currently unused
// var BackendLogID: String? = null, // Currently unused
var OS: String? = null,
var OSVersion: String? = null,
var Env: String? = null,
var Distro: String? = null,
var DistroVersion: String? = null,
var DistroCodeName: String? = null,
var Desktop: Boolean? = null,
var Package: String? = null,
var DeviceModel: String? = null,
var ShareeNode: Boolean? = null,
var Hostname: String? = null,
var ShieldsUp: Boolean? = null,
var NoLogsNoSupport: Boolean? = null,
var Machine: String? = null,
var RoutableIPs: List<Prefix>? = null,
var Services: List<Service>? = null,
// var OSVersion: String? = null, // Currently unused
// var Env: String? = null, // Currently unused
// var Distro: String? = null, // Currently unused
// var DistroVersion: String? = null, // Currently unused
// var DistroCodeName: String? = null, // Currently unused
// var Desktop: Boolean? = null, // Currently unused
// var Package: String? = null, // Currently unused
// var DeviceModel: String? = null, // Currently unused
// var ShareeNode: Boolean? = null, // Currently unused
// var Hostname: String? = null, // Currently unused
// var ShieldsUp: Boolean? = null, // Currently unused
// var NoLogsNoSupport: Boolean? = null, // Currently unused
// var Machine: String? = null, // Currently unused
// var RoutableIPs: List<Prefix>? = null, // Currently unused
// var Services: List<Service>? = null, // Currently unused
var Location: Location? = null,
)
@Serializable
data class Node(
var ID: NodeID,
// var ID: NodeID, // Currently unused
var StableID: StableNodeID,
var Name: String,
var User: UserID,
var Sharer: UserID? = null,
var Key: KeyNodePublic,
// var Sharer: UserID? = null, // Currently unused
// var Key: KeyNodePublic, // Currently unused
var KeyExpiry: String,
var Machine: MachineKey,
// var Machine: MachineKey, // Currently unused
var Addresses: List<Prefix>? = null,
var AllowedIPs: List<Prefix>? = null,
var Endpoints: List<String>? = null,
// var Endpoints: List<String>? = null, // Currently unused
var Hostinfo: Hostinfo,
var Created: Time,
var LastSeen: Time? = null,
// var Created: Time, // Currently unused
// var LastSeen: Time? = null, // Currently unused
var Online: Boolean? = null,
var Capabilities: List<String>? = null,
var ComputedName: String,
var ComputedNameWithHost: String
// var ComputedNameWithHost: String // Currently unused
) {
val isAdmin: Boolean
get() = (Capabilities ?: emptyList()).contains("https://tailscale.com/cap/is-admin")
// isExitNode reproduces the Go logic in local.go peerStatusFromNode
val isExitNode: Boolean =
AllowedIPs?.contains("0.0.0.0/0") ?: false && AllowedIPs?.contains("::/0") ?: false
}
@Serializable
data class Service(var Proto: String, var Port: Int, var Description: String? = null)
// Currently unused
// @Serializable
// data class Service(var Proto: String, var Port: Int, var Description: String? = null)
@Serializable
data class NetworkProfile(var MagicDNSName: String? = null, var DomainName: String? = null)
@ -88,16 +94,17 @@ class Tailcfg {
var Country: String? = null,
var CountryCode: String? = null,
var City: String? = null,
var CityCode: String? = null,
// var CityCode: String? = null, // Currently unused
var Priority: Int? = null
)
@Serializable
data class DNSConfig(
var Resolvers: List<DnsType.Resolver>? = null,
var Routes: Map<String, List<DnsType.Resolver>?>? = null,
var FallbackResolvers: List<DnsType.Resolver>? = null,
var Domains: List<String>? = null,
var Nameservers: List<Addr>? = null
)
// Currently unused
// @Serializable
// data class DNSConfig(
// var Resolvers: List<DnsType.Resolver>? = null,
// var Routes: Map<String, List<DnsType.Resolver>?>? = null,
// var FallbackResolvers: List<DnsType.Resolver>? = null,
// var Domains: List<String>? = null,
// var Nameservers: List<Addr>? = null
// )
}

@ -7,11 +7,11 @@ import kotlinx.serialization.Serializable
typealias Addr = String
typealias Prefix = String
typealias NodeID = Long
typealias KeyNodePublic = String
typealias MachineKey = String
//typealias NodeID = Long // Currently unused
//typealias KeyNodePublic = String // Currently unused
//typealias MachineKey = String // Currently unused
typealias UserID = Long
typealias Time = String
//typealias Time = String // Currently unused
typealias StableNodeID = String
typealias BugReportID = String

@ -6,7 +6,7 @@ package com.tailscale.ipn.ui.notifier
import android.util.Log
import com.tailscale.ipn.ui.model.Ipn
import com.tailscale.ipn.ui.model.Ipn.Notify
import com.tailscale.ipn.ui.model.Netmap
import com.tailscale.ipn.ui.model.NetworkMap
import com.tailscale.ipn.ui.util.set
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
@ -32,7 +32,7 @@ object Notifier {
private val isReady = CompletableDeferred<Boolean>()
val state: StateFlow<Ipn.State> = MutableStateFlow(Ipn.State.NoState)
val netmap: StateFlow<Netmap.NetworkMap?> = MutableStateFlow(null)
val netmap: StateFlow<NetworkMap?> = MutableStateFlow(null)
val prefs: StateFlow<Ipn.Prefs?> = MutableStateFlow(null)
val engineStatus: StateFlow<Ipn.EngineStatus?> = MutableStateFlow(null)
val tailFSShares: StateFlow<Map<String, String>?> = MutableStateFlow(null)

@ -4,7 +4,7 @@
package com.tailscale.ipn.ui.util
import com.tailscale.ipn.ui.model.Netmap
import com.tailscale.ipn.ui.model.NetworkMap
import com.tailscale.ipn.ui.model.Tailcfg
import com.tailscale.ipn.ui.model.UserID
import com.tailscale.ipn.ui.notifier.Notifier
@ -37,7 +37,7 @@ class PeerCategorizer(scope: CoroutineScope) {
}
}
private fun regenerateGroupedPeers(netmap: Netmap.NetworkMap): List<PeerSet> {
private fun regenerateGroupedPeers(netmap: NetworkMap): List<PeerSet> {
val peers: List<Tailcfg.Node> = netmap.Peers ?: return emptyList()
val selfNode = netmap.SelfNode
var grouped = mutableMapOf<UserID, MutableList<Tailcfg.Node>>()

@ -4,17 +4,20 @@
package com.tailscale.ipn.ui.viewModel
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.tailscale.ipn.ui.localapi.Client
import com.tailscale.ipn.ui.model.Ipn
import com.tailscale.ipn.ui.model.StableNodeID
import com.tailscale.ipn.ui.notifier.Notifier
import com.tailscale.ipn.ui.util.LoadingIndicator
import com.tailscale.ipn.ui.util.set
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import java.util.TreeMap
data class ExitNodePickerNav(
@ -52,22 +55,22 @@ class ExitNodePickerViewModel(private val nav: ExitNodePickerNav) : IpnViewModel
val anyActive: StateFlow<Boolean> = MutableStateFlow(false)
init {
Client(viewModelScope).status { result ->
result.onFailure {
Log.e(TAG, "getStatus: ${it.message}")
}.onSuccess {
it.Peer?.values?.let { peers ->
val allNodes = peers.filter { it.ExitNodeOption }.map {
viewModelScope.launch {
Notifier.netmap.combine(Notifier.prefs) { netmap, prefs -> Pair(netmap, prefs) }
.stateIn(viewModelScope).collect { (netmap, prefs) ->
val exitNodeId = prefs?.ExitNodeID
netmap?.Peers?.let { peers ->
val allNodes = peers.filter { it.isExitNode }.map {
ExitNode(
id = it.ID,
label = it.DNSName,
online = it.Online,
selected = it.ExitNode,
mullvad = it.DNSName.endsWith(".mullvad.ts.net."),
priority = it.Location?.Priority ?: 0,
countryCode = it.Location?.CountryCode ?: "",
country = it.Location?.Country ?: "",
city = it.Location?.City ?: "",
id = it.StableID,
label = it.Name,
online = it.Online ?: false,
selected = it.StableID == exitNodeId,
mullvad = it.Name.endsWith(".mullvad.ts.net."),
priority = it.Hostinfo?.Location?.Priority ?: 0,
countryCode = it.Hostinfo?.Location?.CountryCode ?: "",
country = it.Hostinfo?.Location?.Country ?: "",
city = it.Hostinfo?.Location?.City ?: "",
)
}

Loading…
Cancel
Save