peerdiscovery

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2026 License: AGPL-3.0 Imports: 25 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	DNSService = "_concord._udp"
	DNSPort    = "8053"
)

Service identifiers for Concord peer discovery. The format follows RFC 6763.

* MDNSService (_concord._udp) is used for mDNS / LAN discovery. Nodes on the same local network advertise themselves via multicast. Other nodes discover them by browsing for this service.

* DNSService (_concord._udp) names the embedded DNS server's service. Workloads resolve peer names against it; member discovery no longer queries it, gossip carries membership instead.

View Source
var OverlayPrefix = netip.MustParsePrefix("10.0.0.0/16")

OverlayPrefix is the container and WireGuard mesh. Memberlist must not advertise or Join addresses in this range. It must stay disjoint from the underlay NIC.

Functions

func MDNSAdvertise

func MDNSAdvertise(
	ctx context.Context,
	nodeConfig *node.NodeConfig,
) (*mdns.Server, error)

MDNSAdvertise publishes this node on the local network under DNSService. It advertises only the resolved underlay address, the same one memberlist publishes, so peers never learn overlay endpoints such as cn0 or docker0. Advertising all interfaces leaks those addresses into peer candidate pools, and joining them dials the local bridge instead of a peer. A nil IP list falls back to all interfaces and is used only when no usable address resolves.

func ObserveMemberlistPeers

func ObserveMemberlistPeers(
	ctx context.Context,
	logger *zap.Logger,
	localNodeID uuid.UUID,
	memberService *MemberService,
	j journal.Journal,
	views []journalview.View,
)

ObserveMemberlistPeers is passive. It polls the already-joined memberlist every 5 seconds, diffs against the previous snapshot, and records peer.seen, peer.updated, and peer.lost events. It does not query mDNS or DNS SRV and does not call Join. Active discovery is handled by runDiscoveryLoop in runtime. It blocks until context is cancelled.

func ResolveAdvertise

func ResolveAdvertise(
	bind netip.AddrPort,
	advertise netip.Addr,
) netip.AddrPort

ResolveAdvertise picks the address other peers should dial when contacting this node. Callers pass the memberlist bind address and an optional config override.

advertise is the operator-supplied IP hint (AdvertiseAddress from config). Empty (zero Addr) means the runtime picks one from the bind address or a non-loopback interface. The port always comes from bind regardless.

Resolution order: 1. Advertise hint if set (IP from config, port from bind). 2. The bind address if it is already a real IP peers can dial. 3. Otherwise the first usable host interface IP, with the bind port.

Returns the zero AddrPort only if no usable address exists; memberlist will then choose an interface on its own.

Types

type AnchorResolver

type AnchorResolver struct {
	// Self is this node's position, or nil when unknown.
	Self *geo.Point
	// Anchors is the configured rendezvous list from node config.
	Anchors []nodepackage.AnchorEntry
}

AnchorResolver returns configured rendezvous addresses as join candidates. With a known self position it orders nearest-first, so a truck dials its own depot instead of a far anchor that happens to sit first in the list; without one it keeps list order. Entries with out-of-planet coordinates sort last: they may still dial fine, their position just orders nothing. Empty anchors resolve empty, which keeps LAN-only fleets on mDNS alone.

func (AnchorResolver) Resolve

func (a AnchorResolver) Resolve(ctx context.Context) ([]netip.AddrPort, error)

Resolve returns anchor addresses without any I/O: anchors are provisioned, not discovered.

type MDNSResolver

type MDNSResolver struct {
	Timeout time.Duration
}

func (*MDNSResolver) Resolve

func (m *MDNSResolver) Resolve(ctx context.Context) ([]netip.AddrPort, error)

Resolve discovers Concord peers on the local network using multicast DNS. It browses for the configured mDNS service and returns discovered peer addresses.

type MemberService

type MemberService struct {
	// contains filtered or unexported fields
}

MemberService wraps memberlist membership tracking for Concord peer discovery.

func Start

func Start(
	logger *zap.Logger,
	node Node,
	join []netip.AddrPort,
	advertise netip.Addr,
	identity NoiseIdentity,
) (*MemberService, error)

Start creates a memberlist-backed peer discovery service for node.

The node ID is used as memberlist's node name so discovered members can be mapped back to stable Concord identities. The node address is the local bind endpoint for memberlist traffic. Port 0 asks the OS for an ephemeral port; use LocalAddr after Start to learn the bound endpoint.

advertise is an optional IP hint from config overriding which IP to publish (see ResolveAdvertise). A zero Addr means auto-detect.

If join is non-empty, Start attempts to join those bootstrap addresses. A failed join does not abort startup: the node keeps running as a solo member (membership = this node only). Discovery may have returned a non-Concord host; soft-fail avoids taking the whole process down. Callers can retry Join later.

identity is this node's Noise identity for gossip. An empty identity (nil Pub) advertises no Noise key; peers then cannot open Noise sessions to this node until it restarts with an identity.

func (*MemberService) Join

func (m *MemberService) Join(
	existingMembers []netip.AddrPort,
) (int, error)

Join asks the running memberlist service to join existing peer addresses.

The addresses are bootstrap endpoints, not durable peer identities. After a successful join, memberlist exchanges membership state and Members can return the reachable nodes known to the local process.

func (*MemberService) LocalAddr

func (m *MemberService) LocalAddr() (netip.AddrPort, error)

LocalAddr returns the address memberlist is advertising for this node.

func (*MemberService) Members

func (m *MemberService) Members() ([]Node, error)

Members returns the current memberlist view as Concord peer discovery nodes.

Memberlist stores each member's identity in Node.Name. Concord writes UUIDs into that field when starting memberlist, so this method parses the name back into a uuid.UUID and pairs it with the member's advertised address.

func (*MemberService) Publish

func (m *MemberService) Publish() error

Publish pushes current metadata to the mesh when anything volatile changed since the last push. Static identity never differs, so it is not compared; see equalVolatile for the compared set. Unchanged values skip the broadcast entirely. Callers are the fast beat (after sampling) and the discovery beat (after config refresh), hence the mutex: Set* stay lock-free atomics for the gossip hot path while Publish serializes the read-compare-broadcast sequence.

func (*MemberService) SetAnchor

func (m *MemberService) SetAnchor(anchored bool)

SetAnchor marks this node as a rendezvous anchor in gossip metadata.

func (*MemberService) SetPosition

func (m *MemberService) SetPosition(lat, lon float64)

SetPosition updates the geographic position reported in node gossip metadata. Out-of-planet coordinates are ignored, keeping the previous position: callers retry with a real fix next beat. This moves gossip only; history needs Sampler.RecordPosition too. The IPC position setter applies both together.

func (*MemberService) SetPressure

func (m *MemberService) SetPressure(cpu, mem, disk uint8)

SetPressure updates the utilization trio reported in node gossip metadata. Values are 0-100 percents sampled on the fast beat.

func (*MemberService) SetWorkloadCount

func (m *MemberService) SetWorkloadCount(n int)

SetWorkloadCount updates the count of active local workloads reported in node gossip metadata.

func (*MemberService) Shutdown

func (m *MemberService) Shutdown() error

Shutdown stops the memberlist service and releases its network resources.

type MemberlistLogWriter

type MemberlistLogWriter struct {
	// contains filtered or unexported fields
}

Adapter for the message levels coming from the memberlist library

func (*MemberlistLogWriter) Write

func (w *MemberlistLogWriter) Write(p []byte) (int, error)

type Node

type Node struct {
	State    NodeState
	Address  netip.AddrPort
	Metadata NodeMetadata
	ID       uuid.UUID
}

Node identifies a Concord node as it appears in peer discovery.

ID is the stable Concord node identity. Address is the current network endpoint used by memberlist for peer membership traffic. The address can change over time; the ID is the durable identity. State is memberlist's current liveness observation for the node.

type NodeMetadata

type NodeMetadata struct {
	WireGuardPublicKey string  `json:"wireguard_public_key"`
	CPUMHz             float64 `json:"cpu_mhz"`
	MemoryMB           uint64  `json:"memory_mb"`
	Workloads          int     `json:"workload_count"`
	// CPUPercent, MemPercent, and DiskPercent are 0-100 utilization sampled
	// on the fast beat. Small by design: gossip caps metadata at 512 bytes.
	CPUPercent  uint8 `json:"cpu_percent"`
	MemPercent  uint8 `json:"mem_percent"`
	DiskPercent uint8 `json:"disk_percent"`
	// Lat and Lon are this node's geographic position in decimal degrees.
	// Operator-provisioned and stationary in practice; members without a
	// known position gossip zeros, which never order anything.
	Lat float64 `json:"lat"`
	Lon float64 `json:"lon"`
	// Anchor marks a rendezvous anchor: a stable reachable member newcomers
	// may join. Provisioned with concord --anchor.
	Anchor bool `json:"anchor"`
	// NoisePublicKey is this node's Noise static public key (32 raw bytes,
	// base64 in JSON). Dialers use it as the IK handshake's pre-known peer
	// key, so no dial path needs a key the gossip layer did not provide.
	// The CA signature over the parcel travels inside the handshake, not
	// here: at 256 bytes it would blow memberlist's 512-byte metadata limit.
	NoisePublicKey []byte `json:"noise_public_key,omitempty"`
	// NoiseGeneration is the rotation counter of NoisePublicKey. Highest
	// generation seen for a node wins.
	NoiseGeneration uint64 `json:"noise_generation,omitempty"`
}

NodeMetadata carries dynamic metrics and networking capabilities gossiped across the cluster.

type NodeState

type NodeState string

NodeState describes memberlist's current liveness observation for a node.

const (
	NodeStateAlive   NodeState = "alive"
	NodeStateSuspect NodeState = "suspect"
	NodeStateDead    NodeState = "dead"
	NodeStateLeft    NodeState = "left"
	NodeStateUnknown NodeState = "unknown"
)

type NoiseIdentity

type NoiseIdentity struct {
	Pub        []byte
	Signature  []byte
	Generation uint64
}

NoiseIdentity is this node's Noise identity for gossip: the static public key, its rotation generation, and the CA signature binding all three to the node ID. The private half never leaves disk; see transport.EnsureStaticKey.

type Resolver

type Resolver interface {
	Resolve(ctx context.Context) ([]netip.AddrPort, error)
}

Resolver discovers candidate peer addresses for bootstrapping memberlist membership. Different implementations cover different discovery mechanisms such as LAN broadcast or a rendezvous anchor.

Resolve must be safe for concurrent calls from multiple goroutines.

type ResolverFunc

type ResolverFunc func(ctx context.Context) ([]netip.AddrPort, error)

ResolverFunc is an adapter that turns a plain function into a Resolver.

func (ResolverFunc) Resolve

func (f ResolverFunc) Resolve(
	ctx context.Context,
) ([]netip.AddrPort, error)

Resolve calls the underlying function.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL