Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
739 changes: 416 additions & 323 deletions api/agentmesh.pb.go

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions api/agentmesh.proto
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,28 @@ message ControlPlaneInfoResponse {
// placement, and its load as of its last lease, to prefer the ones with
// room.
repeated RouterInfo routers = 6;
// How the mesh keeps its directory. Members and routers read these here
// and nowhere else, so they agree without configuration of their own; a
// control plane that leaves it unset leaves them on the defaults the
// api package names.
DirectoryParameters directory = 7;
}

// DirectoryParameters are the settings of the DHT the routers serve, set
// once per mesh on the control plane (agentmesh-control-plane
// --dht-replication, --dht-reprovide-interval) and published on /info.
message DirectoryParameters {
// How many routers hold each record a member publishes and answer each
// lookup it makes: the routers closest to the key. A member holds a
// session with each of them besides the routers it is attached to.
int32 replication = 1;
// How often a member re-announces each service it publishes.
google.protobuf.Duration reprovide_interval = 2;
// How long a router keeps a record after its last announcement: three
// reprovide intervals, so a provider that stopped without unpublishing
// leaves within it and two missed announcements do not unlist one that
// did not.
google.protobuf.Duration record_ttl = 3;
}

// RouterInfo is one router as the control plane lists it on /info.
Expand Down
102 changes: 102 additions & 0 deletions api/directory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
"fmt"
"time"

"google.golang.org/protobuf/types/known/durationpb"
)

// The directory parameters a mesh runs on when its control plane sets
// none (ControlPlaneInfoResponse.directory): three routers per key, a
// re-announcement every five minutes, and records that outlive three of
// them. Every member and router derives its settings from these three
// numbers, so a member started against no control plane at all behaves as
// one of a default mesh.
const (
DefaultDHTReplication = 3
DefaultReprovideInterval = 5 * time.Minute
recordTTLReprovideMultiple = 3
)

// RecordTTL is how long a router keeps a record after its last
// announcement for a mesh that re-announces every reprovide: three
// intervals, so a provider that stopped without unpublishing leaves within
// it and two missed announcements do not unlist one that did not.
func RecordTTL(reprovide time.Duration) time.Duration {
return recordTTLReprovideMultiple * reprovide
}

// NewDirectoryParameters is the message a control plane publishes for a
// replication factor and a reprovide interval, the TTL derived.
func NewDirectoryParameters(replication int, reprovide time.Duration) *DirectoryParameters {
return &DirectoryParameters{
Replication: int32(replication),
ReprovideInterval: durationpb.New(reprovide),
RecordTtl: durationpb.New(RecordTTL(reprovide)),
}
}

// ValidateDirectoryParameters checks the two numbers an operator sets.
func ValidateDirectoryParameters(replication int, reprovide time.Duration) error {
if replication < 1 {
return fmt.Errorf("dht replication %d: at least one router must hold each record", replication)
}
if reprovide <= 0 {
return fmt.Errorf("dht reprovide interval %s must be positive", reprovide)
}
return nil
}

// DirectorySettings are the directory parameters as a member or router
// applies them.
type DirectorySettings struct {
Replication int
Reprovide time.Duration
RecordTTL time.Duration
}

// DefaultDirectorySettings is what a member or router runs on when the
// control plane publishes no directory parameters.
func DefaultDirectorySettings() DirectorySettings {
return DirectorySettings{
Replication: DefaultDHTReplication,
Reprovide: DefaultReprovideInterval,
RecordTTL: RecordTTL(DefaultReprovideInterval),
}
}

// DirectorySettingsFrom reads the parameters a control plane published,
// with the default for each it left unset or unusable, so a member or
// router never runs with a zero.
func DirectorySettingsFrom(p *DirectoryParameters) DirectorySettings {
s := DefaultDirectorySettings()
if p == nil {
return s
}
if r := int(p.GetReplication()); r >= 1 {
s.Replication = r
}
if d := p.GetReprovideInterval().AsDuration(); p.GetReprovideInterval() != nil && d > 0 {
s.Reprovide = d
s.RecordTTL = RecordTTL(d)
}
if d := p.GetRecordTtl().AsDuration(); p.GetRecordTtl() != nil && d > s.Reprovide {
s.RecordTTL = d
}
return s
}
62 changes: 62 additions & 0 deletions api/directory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
"testing"
"time"

"google.golang.org/protobuf/types/known/durationpb"
)

func TestDirectorySettingsFrom(t *testing.T) {
def := DefaultDirectorySettings()
for _, tc := range []struct {
name string
in *DirectoryParameters
want DirectorySettings
}{
{name: "unset", in: nil, want: def},
{name: "empty", in: &DirectoryParameters{}, want: def},
{name: "published", in: NewDirectoryParameters(5, 20*time.Minute), want: DirectorySettings{Replication: 5, Reprovide: 20 * time.Minute, RecordTTL: time.Hour}},
{name: "ttl not derived", in: &DirectoryParameters{Replication: 2, ReprovideInterval: durationpb.New(10 * time.Minute), RecordTtl: durationpb.New(2 * time.Hour)}, want: DirectorySettings{Replication: 2, Reprovide: 10 * time.Minute, RecordTTL: 2 * time.Hour}},
{name: "ttl not longer than the interval is replaced", in: &DirectoryParameters{ReprovideInterval: durationpb.New(10 * time.Minute), RecordTtl: durationpb.New(10 * time.Minute)}, want: DirectorySettings{Replication: def.Replication, Reprovide: 10 * time.Minute, RecordTTL: 30 * time.Minute}},
{name: "zero and negative keep the defaults", in: &DirectoryParameters{Replication: -1, ReprovideInterval: durationpb.New(-time.Minute)}, want: def},
} {
t.Run(tc.name, func(t *testing.T) {
if got := DirectorySettingsFrom(tc.in); got != tc.want {
t.Fatalf("DirectorySettingsFrom(%v) = %+v, want %+v", tc.in, got, tc.want)
}
})
}
}

func TestValidateDirectoryParameters(t *testing.T) {
for _, tc := range []struct {
replication int
reprovide time.Duration
wantErr bool
}{
{DefaultDHTReplication, DefaultReprovideInterval, false},
{1, time.Second, false},
{0, DefaultReprovideInterval, true},
{DefaultDHTReplication, 0, true},
{DefaultDHTReplication, -time.Minute, true},
} {
if err := ValidateDirectoryParameters(tc.replication, tc.reprovide); (err != nil) != tc.wantErr {
t.Errorf("ValidateDirectoryParameters(%d, %s) = %v, want error %v", tc.replication, tc.reprovide, err, tc.wantErr)
}
}
}
6 changes: 6 additions & 0 deletions cmd/agentmesh-control-plane/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ var (
keyRotationInterval time.Duration
keyGracePeriod time.Duration
leaseDuration time.Duration
dhtReplication int
dhtReprovideInterval time.Duration
biscuitTTL time.Duration
oidcSessionTTL time.Duration
workloadSessionTTL time.Duration
Expand Down Expand Up @@ -130,6 +132,8 @@ func main() {
LeaseDuration: leaseDuration,
KeyRotationInterval: keyRotationInterval,
KeyGracePeriod: keyGracePeriod,
DHTReplication: dhtReplication,
DHTReprovideInterval: dhtReprovideInterval,
InsecureSkipTLSVerify: insecureSkipTLSVerify,
BiscuitTimeout: 10 * time.Second,
BiscuitTTL: biscuitTTL,
Expand Down Expand Up @@ -193,6 +197,8 @@ func main() {
rootCmd.Flags().DurationVar(&keyRotationInterval, "key-rotation-interval", 24*time.Hour, "Key rotation interval (e.g. 24h). 0 disables rotation.")
rootCmd.Flags().DurationVar(&keyGracePeriod, "key-grace-period", 1*time.Hour, "Key grace period for rotated keys.")
rootCmd.Flags().DurationVar(&leaseDuration, "lease-duration", 15*time.Minute, "Router lease registration TTL.")
rootCmd.Flags().IntVar(&dhtReplication, "dht-replication", api.DefaultDHTReplication, "How many routers hold each service a member publishes and answer each lookup: the routers closest to the key. Every member holds a session with each of them besides its --routers. More replicas survive more routers down at once; each one is a session more per key on every member. Published on /info for the whole mesh.")
rootCmd.Flags().DurationVar(&dhtReprovideInterval, "dht-reprovide-interval", api.DefaultReprovideInterval, "How often a member re-announces each service it publishes. A provider that stopped without unpublishing stays listed for three intervals; the mesh pays providers / interval announcements a minute. Published on /info for the whole mesh.")
rootCmd.Flags().DurationVar(&biscuitTTL, "biscuit-ttl", api.BiscuitTokenTTL, "Lifespan minted into every issued Biscuit's expiration fact. Capped to the OIDC token's own expiry when shorter.")
rootCmd.Flags().DurationVar(&oidcSessionTTL, "oidc-session-ttl", api.OIDCSessionTTL, "How long an OIDC enrollment stays refreshable before the identity must re-authenticate with the OIDC provider. Shorter values keep the provider authoritative for offboarding at the cost of more frequent interactive re-enrollment.")
rootCmd.Flags().DurationVar(&workloadSessionTTL, "workload-session-ttl", controlplane.DefaultWorkloadSessionTTL, "How long a workload-issuer enrollment stays refreshable without presenting a fresh platform JWT on /refresh.")
Expand Down
8 changes: 1 addition & 7 deletions cmd/agentmesh-node/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,6 @@ var (
tlsCertFlag string
tlsKeyFlag string
tlsCAFlag string
dhtProviderAddrTTLFlag time.Duration
dhtMaxRecordAgeFlag time.Duration
dhtLookupLimitFlag int
discoveryConcurrencyFlag int
backendProbeTimeoutFlag time.Duration
Expand Down Expand Up @@ -465,8 +463,6 @@ func main() {
RouterConnectTimeout: routerConnectTimeoutFlag,
RequiredRole: api.RoleNode,
ControlPlaneSyncInterval: controlPlaneSyncIntervalFlag,
DHTProviderAddrTTL: dhtProviderAddrTTLFlag,
DHTMaxRecordAge: dhtMaxRecordAgeFlag,
DHTLookupLimit: dhtLookupLimitFlag,
DiscoveryConcurrency: discoveryConcurrencyFlag,
BackendProbeTimeout: backendProbeTimeoutFlag,
Expand Down Expand Up @@ -860,7 +856,7 @@ func main() {
runCmd.Flags().StringVar(&socketPathFlag, "socket-path", "", "Unix socket serving the same API, where the socket's owner-only permissions replace the API token (defaults to <data-dir>/"+node.DefaultSocketName+"; pass an empty value to disable)")
runCmd.Flags().StringVar(&metricsAddrFlag, "metrics-addr", "", "Serve Prometheus /metrics, /healthz and /readyz on this address without authentication (e.g. 0.0.0.0:9090), for scrapers and probes that hold no API token; off by default")
runCmd.Flags().StringVar(&pprofAddrFlag, "pprof-addr", "", "Serve Go runtime profiles under /debug/pprof/ on this address (e.g. 127.0.0.1:6060); unauthenticated and a heap profile is a memory dump, so bind it to loopback and reach it through a port-forward; off by default")
runCmd.Flags().StringVar(&discoveryIntervalFlag, "discovery-interval", node.DefaultDiscoveryInterval, "Delay before the first announcement of this node's services to the DHT, capped at 5s; they are re-announced every 5 minutes after that")
runCmd.Flags().StringVar(&discoveryIntervalFlag, "discovery-interval", node.DefaultDiscoveryInterval, "Delay before the first announcement of this node's services to the DHT, capped at 5s; they are re-announced every reprovide interval of the mesh after that (agentmesh-control-plane --dht-reprovide-interval)")
runCmd.Flags().DurationVar(&monitorBootstrapFlag, "monitor-bootstrap", 2*time.Minute, "Initial wait before monitoring router connection")
runCmd.Flags().DurationVar(&monitorCheckIntervalFlag, "monitor-interval", 1*time.Minute, "Interval for checking router connection")
runCmd.Flags().DurationVar(&autoRelayMinIntervalFlag, "autorelay-min-interval", 30*time.Second, "AutoRelay Min Interval")
Expand Down Expand Up @@ -889,8 +885,6 @@ func main() {
runCmd.Flags().StringVar(&tlsCertFlag, "tls-cert", "", "Path to TLS certificate for sidecar API")
runCmd.Flags().StringVar(&tlsKeyFlag, "tls-key", "", "Path to TLS key for sidecar API")
runCmd.Flags().StringVar(&tlsCAFlag, "tls-ca", "", "Path to TLS CA for sidecar API mTLS")
runCmd.Flags().DurationVar(&dhtProviderAddrTTLFlag, "dht-provider-addr-ttl", node.DefaultDHTProviderAddrTTL, "How long a DHT provider record this node stores lives after its last announcement; longer than the 5m between announcements, three times it by default")
runCmd.Flags().DurationVar(&dhtMaxRecordAgeFlag, "dht-max-record-age", 0, "Maximum age for DHT records (0s uses library default)")
runCmd.Flags().IntVar(&dhtLookupLimitFlag, "dht-lookup-limit", 0, "Maximum number of providers to query from the DHT (0 uses default 20)")
runCmd.Flags().IntVar(&discoveryConcurrencyFlag, "discovery-concurrency", 0, "Max concurrent catalog fetches during discovery (0 uses default 10)")
runCmd.Flags().DurationVar(&controlPlaneSyncIntervalFlag, "control-plane-sync-interval", node.DefaultControlPlaneSyncInterval, "How often signing keys, bans, router addresses and mesh policy are pulled from the control plane. Keep it well below the control plane's --key-grace-period; raise it on large meshes.")
Expand Down
4 changes: 2 additions & 2 deletions cmd/agentmesh-one/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ func main() {

// Embedded control plane tunables.
rootCmd.Flags().DurationVar(&cpTunables.LeaseDuration, "control-plane-lease-duration", 0, "Router lease validity (0 keeps the component default)")
rootCmd.Flags().IntVar(&cpTunables.DHTReplication, "dht-replication", 0, "How many routers hold each service a member publishes and answer each lookup (0 keeps the component default of 3); published on /info for the whole mesh")
rootCmd.Flags().DurationVar(&cpTunables.DHTReprovideInterval, "dht-reprovide-interval", 0, "How often a member re-announces each service it publishes (0 keeps the component default of 5m); a stopped provider stays listed for three intervals; published on /info for the whole mesh")
rootCmd.Flags().DurationVar(&cpTunables.KeyRotationInterval, "control-plane-key-rotation-interval", 0, "Biscuit signing key rotation interval (0 keeps the component default)")
rootCmd.Flags().DurationVar(&cpTunables.KeyGracePeriod, "control-plane-key-grace-period", 0, "How long rotated-out keys stay valid for verification (0 keeps the component default)")
rootCmd.Flags().DurationVar(&cpTunables.BiscuitTTL, "control-plane-biscuit-ttl", 0, "Lifespan minted into issued biscuits (0 keeps the component default)")
Expand All @@ -237,8 +239,6 @@ func main() {
rootCmd.Flags().IntVar(&routerTunables.LowWaterMark, "router-low-watermark", 0, "Connection manager low watermark (0 is 15% below the high watermark)")
rootCmd.Flags().IntVar(&routerTunables.HighWaterMark, "router-high-watermark", 0, "Connection manager high watermark (0 keeps the component default)")
rootCmd.Flags().IntVar(&routerTunables.ConnsPerSourceIP, "router-conns-per-source-ip", 0, "Per-source-IP connection budget (0 follows the high watermark; proxied peers share source IPs)")
rootCmd.Flags().DurationVar(&routerTunables.DHTProviderAddrTTL, "router-dht-provider-addr-ttl", 0, "How long a DHT provider record lives after its last announcement (0 keeps the component default of 15m)")
rootCmd.Flags().DurationVar(&routerTunables.DHTMaxRecordAge, "router-dht-max-record-age", 0, "DHT record max age (0 keeps the library default)")
rootCmd.Flags().DurationVar(&routerTunables.RelayLimitDuration, "router-relay-limit-duration", 0, "Relayed connection lifetime (0 keeps the component default of 1h; use e.g. 24h for longer)")
rootCmd.Flags().Var(&routerTunables.RelayLimitData, "router-relay-limit-data", "Bytes relayed per direction per connection, e.g. 512MiB (0 keeps the component default: no limit)")
rootCmd.Flags().DurationVar(&routerTunables.ShutdownLeaseTTL, "router-shutdown-lease-ttl", router.DefaultShutdownLeaseTTL, "Sent in the embedded router's last lease when it stops: how long it expects to be away, which is how long the control plane keeps listing it (0 sends nothing)")
Expand Down
6 changes: 0 additions & 6 deletions cmd/agentmesh-router/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ var (
connsPerSourceIP int
logLevel string
logFormat string
dhtProviderAddrTTL time.Duration
dhtMaxRecordAge time.Duration
lowWaterMark int
highWaterMark int
metricsAddr string
Expand Down Expand Up @@ -86,8 +84,6 @@ func main() {
KeysDBPath: keysPath,
AllowLoopback: allowLoopback,
ConnsPerSourceIP: connsPerSourceIP,
DHTProviderAddrTTL: dhtProviderAddrTTL,
DHTMaxRecordAge: dhtMaxRecordAge,
LowWaterMark: lowWaterMark,
HighWaterMark: highWaterMark,
MetricsAddr: metricsAddr,
Expand Down Expand Up @@ -146,8 +142,6 @@ func main() {
rootCmd.Flags().IntVar(&connsPerSourceIP, "conns-per-source-ip", 0, "Max inbound connections per source IP; 0 is a quarter of --high-watermark, so filling the router takes at least four addresses. Raise it where more members share one address")
rootCmd.Flags().StringVar(&logLevel, "log-level", "info", "Log level (debug, info, warn, error)")
rootCmd.Flags().StringVar(&logFormat, "log-format", "text", "Log format (text, json)")
rootCmd.Flags().DurationVar(&dhtProviderAddrTTL, "dht-provider-addr-ttl", router.DefaultDHTProviderAddrTTL, "How long a DHT provider record lives after its last announcement")
rootCmd.Flags().DurationVar(&dhtMaxRecordAge, "dht-max-record-age", 0, "Maximum age for DHT records (0s uses library default)")
rootCmd.Flags().IntVar(&lowWaterMark, "low-watermark", 0, "Connections the router trims down to once it is over --high-watermark; 0 is 15% below the high watermark, so a trim sheds that share and no more")
rootCmd.Flags().IntVar(&highWaterMark, "high-watermark", router.DefaultHighWaterMark, "Connections the router accepts before it trims; every member holds one, so this is the size of the mesh")
rootCmd.Flags().StringVar(&metricsAddr, "metrics-addr", "", "Serve Prometheus /metrics, /healthz and /readyz on this address (e.g. 0.0.0.0:9090); unauthenticated, off by default")
Expand Down
Loading
Loading