1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
#![doc = include_str!("../README.md")]
use core::fmt;
/// Indicates the capability level of a Tailscale node. This must be kept in-sync with the
/// CapabilityVersion constants in the Golang codebase (`tailcfg/tailcfg.go`).
///
/// It can be thought of as a node's simple version number; a single monotonically increasing
/// integer, rather than the complex x.y.z-cccccc semver+hash(es) versioning scheme. Whenever a
/// node gains a capability or wants to negotiate a change in semantics with the control plane,
/// peers, or an official frontend (such as LocalAPI in the Golang codebase), bump this number and
/// document what's changed.
///
/// The capability versions 0, 1, 2, and 35 are undefined; you cannot create a
/// [`CapabilityVersion`] with these values.
///
/// Note: Prior to 2022-03-06, this value was known as the "`MapRequest` version", `mapVer`, or "map
/// cap"; you'll still see that name used in comments throughout the Golang codebase.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CapabilityVersion(u16);
impl Default for CapabilityVersion {
#[inline]
fn default() -> Self {
Self::CURRENT
}
}
impl CapabilityVersion {
/// 2020-??-??: implicit compression, keep-alives
pub const V3: Self = Self(3);
/// 2020-??-??: opt-in keep-alives via KeepAlive field, opt-in compression via Compress
pub const V4: Self = Self(4);
/// 2020-10-19: implies IncludeIPv6; delta Peers/UserProfiles, supports MagicDNS
pub const V5: Self = Self(5);
/// 2020-12-07: means MapResponse.PacketFilter nil means unchanged
pub const V6: Self = Self(6);
/// 2020-12-15: FilterRule.SrcIPs accepts CIDRs+ranges, doesn't warn about 0.0.0.0/::
pub const V7: Self = Self(7);
/// 2020-12-19: client can buggily receive IPv6 addresses and routes if beta enabled server-side
pub const V8: Self = Self(8);
/// 2020-12-30: client doesn't auto-add implicit search domains from peers; only
/// DNSConfig.Domains
pub const V9: Self = Self(9);
/// 2021-01-17: client understands MapResponse.PeerSeenChange
pub const V10: Self = Self(10);
/// 2021-03-03: client understands IPv6; multiple default routes, and goroutine dumping
pub const V11: Self = Self(11);
/// 2021-03-04: client understands PingRequest
pub const V12: Self = Self(12);
/// 2021-03-19: client understands FilterRule.IPProto
pub const V13: Self = Self(13);
/// 2021-04-07: client understands DNSConfig.Routes and DNSConfig.Resolvers
pub const V14: Self = Self(14);
/// 2021-04-12: client treats nil MapResponse.DNSConfig as meaning unchanged
pub const V15: Self = Self(15);
/// 2021-04-15: client understands Node.Online, MapResponse.OnlineChange
pub const V16: Self = Self(16);
/// 2021-04-18: MapResponse.Domain empty means unchanged
pub const V17: Self = Self(17);
/// 2021-04-19: MapResponse.Node nil means unchanged (all fields now omitempty)
pub const V18: Self = Self(18);
/// 2021-04-21: MapResponse.Debug.SleepSeconds
pub const V19: Self = Self(19);
/// 2021-06-11: MapResponse.LastSeen used even less
/// (<https://github.com/tailscale/tailscale/issues/2107>)
pub const V20: Self = Self(20);
/// 2021-06-15: added MapResponse.DNSConfig.CertDomains
pub const V21: Self = Self(21);
/// 2021-06-16: added MapResponse.DNSConfig.ExtraRecords
pub const V22: Self = Self(22);
/// 2021-08-25: DNSConfig.Routes values may be empty (for ExtraRecords support in 1.14.1+)
pub const V23: Self = Self(23);
/// 2021-09-18: MapResponse.Health from control to node; node shows in "tailscale status"
pub const V24: Self = Self(24);
/// 2021-11-01: MapResponse.Debug.Exit
pub const V25: Self = Self(25);
/// 2022-01-12: (nothing, just bumping for 1.20.0)
pub const V26: Self = Self(26);
/// 2022-02-18: start of SSHPolicy being respected
pub const V27: Self = Self(27);
/// 2022-03-09: client can communicate over Noise.
pub const V28: Self = Self(28);
/// 2022-03-21: MapResponse.PopBrowserURL
pub const V29: Self = Self(29);
/// 2022-03-22: client can request id tokens.
pub const V30: Self = Self(30);
/// 2022-04-15: PingRequest & PingResponse TSMP & disco support
pub const V31: Self = Self(31);
/// 2022-04-17: client knows FilterRule.CapMatch
pub const V32: Self = Self(32);
/// 2022-07-20: added MapResponse.PeersChangedPatch (DERPRegion + Endpoints)
pub const V33: Self = Self(33);
/// 2022-08-02: client understands CapabilityFileSharingTarget
pub const V34: Self = Self(34);
/// 2022-08-02: added PeersChangedPatch.{Key,DiscoKey,Online,LastSeen,KeyExpiry,Capabilities}
pub const V36: Self = Self(36);
/// 2022-08-09: added Debug.{SetForceBackgroundSTUN,SetRandomizeClientPort}; Debug are sticky
pub const V37: Self = Self(37);
/// 2022-08-11: added PingRequest.URLIsNoise
pub const V38: Self = Self(38);
/// 2022-08-15: clients can talk Noise over arbitrary HTTPS port
pub const V39: Self = Self(39);
/// 2022-08-22: added Node.KeySignature, PeersChangedPatch.KeySignature
pub const V40: Self = Self(40);
/// 2022-08-30: uses 100.100.100.100 for route-less ExtraRecords if global nameservers is set
pub const V41: Self = Self(41);
/// 2022-09-06: NextDNS DoH support; see <https://github.com/tailscale/tailscale/pull/5556>
pub const V42: Self = Self(42);
/// 2022-09-21: clients can return usernames for SSH
pub const V43: Self = Self(43);
/// 2022-09-22: MapResponse.ControlDialPlan
pub const V44: Self = Self(44);
/// 2022-09-26: c2n /debug/{goroutines,prefs,metrics}
pub const V45: Self = Self(45);
/// 2022-10-04: c2n /debug/component-logging
pub const V46: Self = Self(46);
/// 2022-10-11: Register{Request,Response}.NodeKeySignature
pub const V47: Self = Self(47);
/// 2022-11-02: Node.UnsignedPeerAPIOnly
pub const V48: Self = Self(48);
/// 2022-11-03: Client understands EarlyNoise
pub const V49: Self = Self(49);
/// 2022-11-14: Client understands CapabilityIngress
pub const V50: Self = Self(50);
/// 2022-11-30: Client understands CapabilityTailnetLockAlpha
pub const V51: Self = Self(51);
/// 2023-01-05: client can handle c2n POST /logtail/flush
pub const V52: Self = Self(52);
/// 2023-01-18: client respects explicit Node.Expired + auto-sets based on Node.KeyExpiry
pub const V53: Self = Self(53);
/// 2023-01-19: Node.Cap added, PeersChangedPatch.Cap, uses Node.Cap for ExitDNS before
/// Hostinfo.Services fallback
pub const V54: Self = Self(54);
/// 2023-01-23: start of c2n GET+POST /update handler
pub const V55: Self = Self(55);
/// 2023-01-24: Client understands CapabilityDebugTSDNSResolution
pub const V56: Self = Self(56);
/// 2023-01-25: Client understands CapabilityBindToInterfaceByRoute
pub const V57: Self = Self(57);
/// 2023-03-10: Client retries lite map updates before restarting map poll.
pub const V58: Self = Self(58);
/// 2023-03-16: Client understands Peers[].SelfNodeV4MasqAddrForThisPeer
pub const V59: Self = Self(59);
/// 2023-04-06: Client understands IsWireGuardOnly
pub const V60: Self = Self(60);
/// 2023-04-18: Client understand SSHAction.SSHRecorderFailureAction
pub const V61: Self = Self(61);
/// 2023-05-05: Client can notify control over noise for SSHEventNotificationRequest recording
/// failure events
pub const V62: Self = Self(62);
/// 2023-06-08: Client understands SSHAction.AllowRemotePortForwarding.
pub const V63: Self = Self(63);
/// 2023-07-11: Client understands s/CapabilityTailnetLockAlpha/CapabilityTailnetLock
pub const V64: Self = Self(64);
/// 2023-07-12: Client understands DERPMap.HomeParams + incremental DERPMap updates with params
pub const V65: Self = Self(65);
/// 2023-07-23: UserProfile.Groups added (available via WhoIs) (removed in 87)
pub const V66: Self = Self(66);
/// 2023-07-25: Client understands PeerCapMap
pub const V67: Self = Self(67);
/// 2023-08-09: Client has dedicated updateRoutine; MapRequest.Stream true means ignore
/// Hostinfo+Endpoints
pub const V68: Self = Self(68);
/// 2023-08-16: removed Debug.LogHeap* + GoroutineDumpURL; added c2n /debug/logheap
pub const V69: Self = Self(69);
/// 2023-08-16: removed most Debug fields; added NodeAttrDisable*, NodeAttrDebug* instead
pub const V70: Self = Self(70);
/// 2023-08-17: added NodeAttrOneCGNATEnable, NodeAttrOneCGNATDisable
pub const V71: Self = Self(71);
/// 2023-08-23: TS-2023-006 UPnP issue fixed; UPnP can now be used again
pub const V72: Self = Self(72);
/// 2023-09-01: Non-Windows clients expect to receive ClientVersion
pub const V73: Self = Self(73);
/// 2023-09-18: Client understands NodeCapMap
pub const V74: Self = Self(74);
/// 2023-09-12: Client understands NodeAttrDNSForwarderDisableTCPRetries
pub const V75: Self = Self(75);
/// 2023-09-20: Client understands ExitNodeDNSResolvers for IsWireGuardOnly nodes
pub const V76: Self = Self(76);
/// 2023-10-03: Client understands Peers[].SelfNodeV6MasqAddrForThisPeer
pub const V77: Self = Self(77);
/// 2023-10-05: can handle c2n Wake-on-LAN sending
pub const V78: Self = Self(78);
/// 2023-10-05: Client understands UrgentSecurityUpdate in ClientVersion
pub const V79: Self = Self(79);
/// 2023-11-16: can handle c2n GET /tls-cert-status
pub const V80: Self = Self(80);
/// 2023-11-17: MapResponse.PacketFilters (incremental packet filter updates)
pub const V81: Self = Self(81);
/// 2023-12-01: Client understands NodeAttrLinuxMustUseIPTables, NodeAttrLinuxMustUseNfTables,
/// c2n /netfilter-kind
pub const V82: Self = Self(82);
/// 2023-12-18: Client understands DefaultAutoUpdate
pub const V83: Self = Self(83);
/// 2024-01-04: Client understands SeamlessKeyRenewal
pub const V84: Self = Self(84);
/// 2024-01-05: Client understands MaxKeyDuration
pub const V85: Self = Self(85);
/// 2024-01-23: Client understands NodeAttrProbeUDPLifetime
pub const V86: Self = Self(86);
/// 2024-02-11: UserProfile.Groups removed (added in 66)
pub const V87: Self = Self(87);
/// 2024-03-05: Client understands NodeAttrSuggestExitNode
pub const V88: Self = Self(88);
/// 2024-03-23: Client no longer respects deleted PeerChange.Capabilities (use CapMap)
pub const V89: Self = Self(89);
/// 2024-04-03: Client understands PeerCapabilityTaildrive.
pub const V90: Self = Self(90);
/// 2024-04-24: Client understands PeerCapabilityTaildriveSharer.
pub const V91: Self = Self(91);
/// 2024-05-06: Client understands NodeAttrUserDialUseRoutes.
pub const V92: Self = Self(92);
/// 2024-05-06: added support for stateful firewalling.
pub const V93: Self = Self(93);
/// 2024-05-06: Client understands Node.IsJailed.
pub const V94: Self = Self(94);
/// 2024-05-06: Client uses NodeAttrUserDialUseRoutes to change DNS dialing behavior.
pub const V95: Self = Self(95);
/// 2024-05-29: Client understands NodeAttrSSHBehaviorV1
pub const V96: Self = Self(96);
/// 2024-06-06: Client understands NodeAttrDisableSplitDNSWhenNoCustomResolvers
pub const V97: Self = Self(97);
/// 2024-06-13: iOS/tvOS clients may provide serial number as part of posture information
pub const V98: Self = Self(98);
/// 2024-06-14: Client understands NodeAttrDisableLocalDNSOverrideViaNRPT
pub const V99: Self = Self(99);
/// 2024-06-18: Initial support for filtertype.Match.SrcCaps - actually usable in capver 109
/// (issue #12542)
pub const V100: Self = Self(100);
/// 2024-07-01: Client supports SSH agent forwarding when handling connections with /bin/su
pub const V101: Self = Self(101);
/// 2024-07-12: NodeAttrDisableMagicSockCryptoRouting support
pub const V102: Self = Self(102);
/// 2024-07-24: Client supports NodeAttrDisableCaptivePortalDetection
pub const V103: Self = Self(103);
/// 2024-08-03: SelfNodeV6MasqAddrForThisPeer now works
pub const V104: Self = Self(104);
/// 2024-08-05: Fixed SSH behavior on systems that use busybox (issue #12849)
pub const V105: Self = Self(105);
/// 2024-09-03: fix panic regression from cryptokey routing change (65fe0ba7b5)
pub const V106: Self = Self(106);
/// 2024-10-30: add App Connector to conffile (PR #13942)
pub const V107: Self = Self(107);
/// 2024-11-08: Client sends ServicesHash in Hostinfo, understands c2n GET /vip-services.
pub const V108: Self = Self(108);
/// 2024-11-18: Client supports filtertype.Match.SrcCaps (issue #12542)
pub const V109: Self = Self(109);
/// 2024-12-12: removed never-before-used Tailscale SSH public key support (#14373)
pub const V110: Self = Self(110);
/// 2025-01-14: Client supports a peer having Node.HomeDERP (issue #14636)
pub const V111: Self = Self(111);
/// 2025-01-14: Client interprets AllowedIPs of nil as meaning same as Addresses
pub const V112: Self = Self(112);
/// 2025-01-20: Client communicates to control whether funnel is enabled by sending
/// Hostinfo.IngressEnabled (#14688)
pub const V113: Self = Self(113);
/// 2025-01-30: NodeAttrMaxKeyDuration CapMap defined, clients might use it (no tailscaled code
/// change) (#14829)
pub const V114: Self = Self(114);
/// 2025-03-07: Client understands DERPRegion.NoMeasureNoHome.
pub const V115: Self = Self(115);
/// 2025-05-05: Client serves MagicDNS "AAAA" if NodeAttrMagicDNSPeerAAAA set on self node
pub const V116: Self = Self(116);
/// 2025-05-28: Client understands DisplayMessages (structured health messages), but not
/// necessarily PrimaryAction.
pub const V117: Self = Self(117);
/// 2025-07-01: Client sends Hostinfo.StateEncrypted to report whether the state file is
/// encrypted at rest (#15830)
pub const V118: Self = Self(118);
/// 2025-07-10: Client uses Hostinfo.Location.Priority to prioritize one route over another.
pub const V119: Self = Self(119);
/// 2025-07-15: Client understands peer relay disco messages, and implements peer client and
/// relay server functions
pub const V120: Self = Self(120);
/// 2025-07-19: Client understands peer relay endpoint alloc with
/// `disco.AllocateUDPRelayEndpointRequest` & `disco.AllocateUDPRelayEndpointResponse`
pub const V121: Self = Self(121);
/// 2025-07-21: Client sends Hostinfo.ExitNodeID to report which exit node it has selected, if any.
pub const V122: Self = Self(122);
/// 2025-07-28: fix deadlock regression from cryptokey routing change (issue #16651)
pub const V123: Self = Self(123);
/// 2025-08-08: removed NodeAttrDisableMagicSockCryptoRouting support, crypto routing is now mandatory
pub const V124: Self = Self(124);
/// 2025-08-11: dnstype.Resolver adds UseWithExitNode field.
pub const V125: Self = Self(125);
/// 2025-09-17: Client uses seamless key renewal unless disabled by control
/// (tailscale/corp#31479)
pub const V126: Self = Self(126);
/// 2025-09-19: can handle C2N /debug/netmap.
pub const V127: Self = Self(127);
/// 2025-10-02: can handle C2N /debug/health.
pub const V128: Self = Self(128);
/// 2025-10-04: Fixed sleep/wake deadlock in magicsock when using peer relay (PR #17449)
pub const V129: Self = Self(129);
/// 2025-10-06: client can send key.HardwareAttestationPublic and
/// key.HardwareAttestationKeySignature in MapRequest
pub const V130: Self = Self(130);
/// 2025-11-25: client respects NodeAttrDefaultAutoUpdate
pub const V131: Self = Self(131);
/// 2026-02-13: client respects NodeAttrDisableHostsFileUpdates
pub const V132: Self = Self(132);
/// 2026-02-17: client understands NodeAttrForceRegisterMagicDNSIPv4Only; MagicDNS IPv6
/// registered w/ OS by default
pub const V133: Self = Self(133);
/// The current capability version of this Tailscale node.
pub const CURRENT: Self = Self::V130;
/// Create a new [`CapabilityVersion`] instance from a `u16`. Note that the versions 0, 1, 2,
/// and 35 are undefined and will result in an error.
pub const fn new(value: u16) -> Option<Self> {
match value {
v if v < 3 || v == 35 => None,
_ => Some(Self(value)),
}
}
}
impl fmt::Display for CapabilityVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Convert [`CapabilityVersion`] into a native-endian [`u16`]; little-endian on `x86_64`.
impl From<CapabilityVersion> for u16 {
fn from(value: CapabilityVersion) -> Self {
value.0
}
}
impl TryFrom<u16> for CapabilityVersion {
type Error = ();
fn try_from(value: u16) -> Result<Self, Self::Error> {
Self::new(value).ok_or(())
}
}