summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--tailcfg/tailcfg.go48
-rw-r--r--tailcfg/tailcfg_clone.go3
-rw-r--r--tailcfg/tailcfg_test.go2
-rw-r--r--tailcfg/tailcfg_view.go18
-rw-r--r--util/deephash/deephash_test.go4
5 files changed, 65 insertions, 10 deletions
diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go
index ab254a57e..bbbe7dfff 100644
--- a/tailcfg/tailcfg.go
+++ b/tailcfg/tailcfg.go
@@ -241,6 +241,15 @@ type Node struct {
// DataPlaneAuditLogID is the per-node logtail ID used for data plane audit logging.
DataPlaneAuditLogID string `json:",omitempty"`
+
+ // Accumulators are scoped accumulator values allocated to this node.
+ Accumulators []Accumulator `json:",omitempty"`
+}
+
+// Accumulator is a scoped monotonically-increasing accumulator value.
+type Accumulator struct {
+ Scope string // default scope is "*"
+ Accumulator uint64 // allocated values are greater than zero
}
// DisplayName returns the user-facing name for a node which should
@@ -513,6 +522,7 @@ type Hostinfo struct {
Cloud string `json:",omitempty"`
Userspace opt.Bool `json:",omitempty"` // if the client is running in userspace (netstack) mode
UserspaceRouter opt.Bool `json:",omitempty"` // if the client's subnet router is running in userspace (netstack) mode
+ Accumulator opt.Bool `json:",omitempty"` // node participates in tailnet accumulator store
// NOTE: any new fields containing pointers in this type
// require changes to Hostinfo.Equal.
@@ -1391,6 +1401,10 @@ type MapResponse struct {
// server. An initial nil is equivalent to new(ControlDialPlan).
// A subsequent streamed nil means no change.
ControlDialPlan *ControlDialPlan `json:",omitempty"`
+
+ // MaxAccumulator is the maximum accumulator allocated in this
+ // tailnet for each scope.
+ MaxAccumulators []Accumulator `json:",omitempty"`
}
// ControlDialPlan is instructions from the control server to the client on how
@@ -1557,7 +1571,8 @@ func (n *Node) Equal(n2 *Node) bool {
n.ComputedName == n2.ComputedName &&
n.computedHostIfDifferent == n2.computedHostIfDifferent &&
n.ComputedNameWithHost == n2.ComputedNameWithHost &&
- eqStrings(n.Tags, n2.Tags)
+ eqStrings(n.Tags, n2.Tags) &&
+ eqAccumulators(n.Accumulators, n2.Accumulators)
}
func eqBoolPtr(a, b *bool) bool {
@@ -1571,6 +1586,18 @@ func eqBoolPtr(a, b *bool) bool {
}
+func eqAccumulators(a, b []Accumulator) bool {
+ if len(a) != len(b) || ((a == nil) != (b == nil)) {
+ return false
+ }
+ for i, v := range a {
+ if v != b[i] {
+ return false
+ }
+ }
+ return true
+}
+
func eqStrings(a, b []string) bool {
if len(a) != len(b) || ((a == nil) != (b == nil)) {
return false
@@ -1846,6 +1873,25 @@ type OverTLSPublicKeyResponse struct {
PublicKey key.MachinePublic `json:"publicKey"`
}
+// AccumulatorRequest requests the accumulator for a scope be incremented.
+//
+// It is JSON-encoded and sent over Noise to "/machine/accumulator".
+type AccumulatorRequest struct {
+ CapVersion CapabilityVersion // the clients' current CapabilityVersion
+ NodeKey key.NodePublic // the client's current node key
+ Scope string // always "*" for now
+ CurValue uint64
+ // ToValue, if set, means the node does not want to bump the
+ // MaxAccumulator, but has upgraded its own local state to ToValue.
+ ToValue uint64 `json:",omitempty"`
+}
+
+// An AccumulatorResponse is the response to an AccumulatorRequest,
+// it is sent when the value is successfully incremented.
+type AccumulatorResponse struct {
+ NewValue uint64
+}
+
// TokenRequest is a request to get an OIDC ID token for an audience.
// The token can be presented to any resource provider which offers OIDC
// Federation.
diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go
index daec87fc9..f32ee304f 100644
--- a/tailcfg/tailcfg_clone.go
+++ b/tailcfg/tailcfg_clone.go
@@ -64,6 +64,7 @@ func (src *Node) Clone() *Node {
*dst.Online = *src.Online
}
dst.Capabilities = append(src.Capabilities[:0:0], src.Capabilities...)
+ dst.Accumulators = append(src.Accumulators[:0:0], src.Accumulators...)
return dst
}
@@ -96,6 +97,7 @@ var _NodeCloneNeedsRegeneration = Node(struct {
computedHostIfDifferent string
ComputedNameWithHost string
DataPlaneAuditLogID string
+ Accumulators []Accumulator
}{})
// Clone makes a deep copy of Hostinfo.
@@ -143,6 +145,7 @@ var _HostinfoCloneNeedsRegeneration = Hostinfo(struct {
Cloud string
Userspace opt.Bool
UserspaceRouter opt.Bool
+ Accumulator opt.Bool
}{})
// Clone makes a deep copy of NetInfo.
diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go
index a7dcb93c9..8c0d43957 100644
--- a/tailcfg/tailcfg_test.go
+++ b/tailcfg/tailcfg_test.go
@@ -58,6 +58,7 @@ func TestHostinfoEqual(t *testing.T) {
"Cloud",
"Userspace",
"UserspaceRouter",
+ "Accumulator",
}
if have := fieldsOf(reflect.TypeOf(Hostinfo{})); !reflect.DeepEqual(have, hiHandles) {
t.Errorf("Hostinfo.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
@@ -333,6 +334,7 @@ func TestNodeEqual(t *testing.T) {
"Capabilities",
"ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
"DataPlaneAuditLogID",
+ "Accumulators",
}
if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) {
t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
diff --git a/tailcfg/tailcfg_view.go b/tailcfg/tailcfg_view.go
index 30da1d02b..97b29d718 100644
--- a/tailcfg/tailcfg_view.go
+++ b/tailcfg/tailcfg_view.go
@@ -168,13 +168,14 @@ func (v NodeView) Online() *bool {
return &x
}
-func (v NodeView) KeepAlive() bool { return v.ж.KeepAlive }
-func (v NodeView) MachineAuthorized() bool { return v.ж.MachineAuthorized }
-func (v NodeView) Capabilities() views.Slice[string] { return views.SliceOf(v.ж.Capabilities) }
-func (v NodeView) ComputedName() string { return v.ж.ComputedName }
-func (v NodeView) ComputedNameWithHost() string { return v.ж.ComputedNameWithHost }
-func (v NodeView) DataPlaneAuditLogID() string { return v.ж.DataPlaneAuditLogID }
-func (v NodeView) Equal(v2 NodeView) bool { return v.ж.Equal(v2.ж) }
+func (v NodeView) KeepAlive() bool { return v.ж.KeepAlive }
+func (v NodeView) MachineAuthorized() bool { return v.ж.MachineAuthorized }
+func (v NodeView) Capabilities() views.Slice[string] { return views.SliceOf(v.ж.Capabilities) }
+func (v NodeView) ComputedName() string { return v.ж.ComputedName }
+func (v NodeView) ComputedNameWithHost() string { return v.ж.ComputedNameWithHost }
+func (v NodeView) DataPlaneAuditLogID() string { return v.ж.DataPlaneAuditLogID }
+func (v NodeView) Accumulators() views.Slice[Accumulator] { return views.SliceOf(v.ж.Accumulators) }
+func (v NodeView) Equal(v2 NodeView) bool { return v.ж.Equal(v2.ж) }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _NodeViewNeedsRegeneration = Node(struct {
@@ -205,6 +206,7 @@ var _NodeViewNeedsRegeneration = Node(struct {
computedHostIfDifferent string
ComputedNameWithHost string
DataPlaneAuditLogID string
+ Accumulators []Accumulator
}{})
// View returns a readonly view of Hostinfo.
@@ -281,6 +283,7 @@ func (v HostinfoView) SSH_HostKeys() views.Slice[string] { return views.SliceOf(
func (v HostinfoView) Cloud() string { return v.ж.Cloud }
func (v HostinfoView) Userspace() opt.Bool { return v.ж.Userspace }
func (v HostinfoView) UserspaceRouter() opt.Bool { return v.ж.UserspaceRouter }
+func (v HostinfoView) Accumulator() opt.Bool { return v.ж.Accumulator }
func (v HostinfoView) Equal(v2 HostinfoView) bool { return v.ж.Equal(v2.ж) }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
@@ -312,6 +315,7 @@ var _HostinfoViewNeedsRegeneration = Hostinfo(struct {
Cloud string
Userspace opt.Bool
UserspaceRouter opt.Bool
+ Accumulator opt.Bool
}{})
// View returns a readonly view of NetInfo.
diff --git a/util/deephash/deephash_test.go b/util/deephash/deephash_test.go
index c085d28b9..83761505e 100644
--- a/util/deephash/deephash_test.go
+++ b/util/deephash/deephash_test.go
@@ -575,7 +575,7 @@ func TestGetTypeHasher(t *testing.T) {
{
name: "tailcfg.Node",
val: &tailcfg.Node{},
- out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
},
}
for _, tt := range tests {
@@ -594,7 +594,7 @@ func TestGetTypeHasher(t *testing.T) {
}
h.sum()
if got := string(hb.B); got != tt.out {
- t.Fatalf("got %q; want %q", got, tt.out)
+ t.Fatalf("\n got %q;\nwant %q", got, tt.out)
}
})
}