Tighten host_query comments to match project style

https://claude.ai/code/session_01Nibp24Pgk2JMue8VyWHq7o
This commit is contained in:
Claude
2026-06-13 01:52:58 +00:00
committed by JackDoan
parent e87ccdc9ea
commit 88e6c2eedf
2 changed files with 73 additions and 102 deletions
+53 -82
View File
@@ -22,20 +22,17 @@ import (
"github.com/slackhq/nebula/config"
)
// hostQueryServer owns the local host query API: a small HTTP+JSON listener
// on a unix socket or tcp address that lets other programs on this machine
// resolve a vpn address to its certificate identity (name, groups, networks)
// for making authorization decisions. It mirrors the lifecycle shape of
// statsServer: constructor wires the reload callback, reload records config,
// Start builds and runs the runtime, Stop tears it down.
// hostQueryServer is a small http+json listener on a unix socket or tcp address that lets other
// programs on this machine resolve a vpn address to its certificate identity (name, groups, networks)
// for making authorization decisions. Lifecycle works like statsServer: the constructor wires the
// reload callback, reload records config, Start runs the runtime, Stop tears it down
type hostQueryServer struct {
l *slog.Logger
ctx context.Context
hostMap *HostMap
pki *PKI
// enabled mirrors `host_query.enabled`. Start consults it so callers
// don't need to know the gating rules.
// enabled mirrors `host_query.enabled` so callers of Start don't need to know the gating rules
enabled atomic.Bool
runMu sync.Mutex
@@ -43,35 +40,28 @@ type hostQueryServer struct {
run *hostQueryRuntime // non-nil while a runtime is live
}
// hostQueryRuntime is the live state owned by a single Start invocation.
// Start stashes a pointer under runMu; Stop and Start's own exit path use
// pointer equality to tell "my runtime" apart from one that replaced it
// after a reload.
// hostQueryRuntime is the live state owned by a single Start invocation. Stop and Start's exit path
// use pointer equality to tell "my runtime" apart from one that replaced it after a reload
type hostQueryRuntime struct {
server *http.Server
listener net.Listener
}
// hostQueryConfig is the snapshot of host_query config that drives the
// runtime. It is comparable with == so reload can detect "no change" cheaply.
// hostQueryConfig is a snapshot of the host_query config section, comparable with == so reload can
// detect "no change" cheaply
type hostQueryConfig struct {
enabled bool
listen string // raw config value, for error messages
network string // "unix" or "tcp"
addr string // socket path or host:port
// socketMode is the file mode applied to the unix socket after bind.
// file mode applied to the unix socket after bind
socketMode fs.FileMode
}
// newHostQueryServerFromConfig builds a hostQueryServer, applies the initial
// config, and registers a reload callback. The reload callback is registered
// before the initial config is applied so a SIGHUP can later enable, fix, or
// disable the listener even if the initial application failed.
//
// Construction never binds the listener; that happens in Start, so config
// tests are side effect free. Start is safe to call unconditionally: it
// no-ops when the host query API is disabled. The returned pointer is always
// non-nil, even on error.
// newHostQueryServerFromConfig builds a hostQueryServer and applies the initial config. The reload
// callback is registered first so a SIGHUP can later enable, fix, or disable the listener even if
// the initial config was bad. Nothing binds until Start, so config tests are side effect free.
// The returned pointer is always non-nil, even on error
func newHostQueryServerFromConfig(ctx context.Context, l *slog.Logger, pki *PKI, hostMap *HostMap, c *config.C) (*hostQueryServer, error) {
h := &hostQueryServer{
l: l,
@@ -92,14 +82,9 @@ func newHostQueryServerFromConfig(ctx context.Context, l *slog.Logger, pki *PKI,
return h, nil
}
// reload records the latest config. On the initial call it only records it;
// Control.Start is what launches the first runtime via hostQueryStart. On
// later calls it reconciles the running runtime with the new config:
//
// - newly enabled -> spawn Start
// - newly disabled -> Stop the runtime
// - config changed (still enabled) -> Stop the old, Start the new
// - no change -> no-op
// reload records the latest config. The initial call only records it, Control.Start launches the
// first runtime via hostQueryStart. Later calls reconcile the running listener with the new config:
// enable, disable, or restart when the listen config changed
func (h *hostQueryServer) reload(c *config.C, initial bool) error {
newCfg, err := loadHostQueryConfig(c)
if err != nil {
@@ -127,9 +112,8 @@ func (h *hostQueryServer) reload(c *config.C, initial bool) error {
return nil
}
// Start binds the listener from the latest config and serves until Stop is
// called or ctx fires. Safe to call when the host query API is disabled or
// already running (both no-op).
// Start binds the listener from the latest config and serves until Stop is called or ctx fires.
// Safe to call when disabled or already running (both no-op)
func (h *hostQueryServer) Start() {
if !h.enabled.Load() {
return
@@ -143,8 +127,7 @@ func (h *hostQueryServer) Start() {
cfg := *h.runCfg
ln, err := h.listen(cfg)
if err != nil {
// Drop the cached config so a SIGHUP with the same config re-triggers
// Start once the user fixes the underlying problem.
// drop the cached config so a SIGHUP with the same config retries the bind
h.runCfg = nil
h.runMu.Unlock()
h.l.Error("Failed to start host query listener", "listen", cfg.listen, "error", err)
@@ -162,19 +145,17 @@ func (h *hostQueryServer) Start() {
h.l.Info("Starting host query listener", "network", cfg.network, "addr", ln.Addr())
cleanExit := h.serve(srv, ln)
// A Stop that raced our bind shut the server down before Serve could
// adopt the listener; closing it again is harmless and guarantees a unix
// socket file gets unlinked.
// A Stop that raced our bind shut the server down before Serve could adopt the listener;
// closing it again is harmless and guarantees a unix socket file gets unlinked
_ = ln.Close()
// Clear our runtime only if nothing has replaced it. Stop races through
// here too but leaves h.run == nil, so the pointer check skips.
// Clear our runtime only if nothing has replaced it. Stop races through here too but leaves
// h.run == nil, so the pointer check skips
h.runMu.Lock()
if h.run == rt {
h.run = nil
// A listener that exited with an error leaves runCfg cached as if it
// were applied. Drop it so a SIGHUP with the same config re-triggers
// Start once the user fixes the underlying problem.
// an error exit leaves runCfg cached as if it were applied, drop it so a SIGHUP with the
// same config re-triggers Start once the user fixes the underlying problem
if !cleanExit {
h.runCfg = nil
}
@@ -182,13 +163,11 @@ func (h *hostQueryServer) Start() {
h.runMu.Unlock()
}
// serve runs srv.Serve and ensures ctx cancellation unblocks it. Returns true
// if the listener exited cleanly (Stop, ctx cancellation, or any other
// http.ErrServerClosed path), false on an unexpected error.
// serve runs srv.Serve and ensures ctx cancellation unblocks it. Returns true if the listener
// exited cleanly (Stop, ctx cancellation), false on an unexpected error
func (h *hostQueryServer) serve(srv *http.Server, ln net.Listener) bool {
// Per-invocation watcher: ctx cancellation triggers a server shutdown
// which in turn unblocks Serve. Closing `done` on exit keeps the watcher
// from outliving this call.
// ctx cancellation triggers a server shutdown which in turn unblocks Serve, closing `done` on
// exit keeps the watcher from outliving this call
done := make(chan struct{})
go func() {
select {
@@ -211,7 +190,7 @@ func (h *hostQueryServer) serve(srv *http.Server, ln net.Listener) bool {
return false
}
// Stop tears down the active runtime, if any. Idempotent.
// Stop tears down the active runtime, if any. Idempotent
func (h *hostQueryServer) Stop() {
h.runMu.Lock()
rt := h.run
@@ -227,9 +206,8 @@ func (h *hostQueryServer) Stop() {
}
}
// listen binds the configured address. For unix sockets it also clears a
// stale socket file left by an unclean exit and applies the configured file
// mode.
// listen binds the configured address. For unix sockets it also clears a stale socket file left by
// an unclean exit and applies the configured file mode
func (h *hostQueryServer) listen(cfg hostQueryConfig) (net.Listener, error) {
if cfg.network == "unix" {
return h.listenUnix(cfg)
@@ -249,9 +227,8 @@ func (h *hostQueryServer) listenUnix(cfg hostQueryConfig) (net.Listener, error)
if fi.Mode()&os.ModeSocket == 0 {
return nil, fmt.Errorf("host_query.listen path %s exists and is not a socket, refusing to replace it", cfg.addr)
}
// A normal shutdown unlinks the socket (unlink-on-close), so a file
// here means a previous process exited uncleanly. Remove it so the
// bind below can succeed.
// a normal shutdown unlinks the socket, so a file here means a previous process exited
// uncleanly, remove it so the bind below can succeed
if err = os.Remove(cfg.addr); err != nil {
return nil, fmt.Errorf("failed to remove stale socket %s: %w", cfg.addr, err)
}
@@ -261,9 +238,8 @@ func (h *hostQueryServer) listenUnix(cfg hostQueryConfig) (net.Listener, error)
if err != nil {
return nil, err
}
// The socket is briefly live with umask-derived permissions before this
// chmod lands; tolerated because connections accepted in that window
// still only reach this read-only API.
// The socket is briefly live with umask-derived permissions before this chmod lands, tolerated
// because connections accepted in that window still only reach this read-only API
if err = os.Chmod(cfg.addr, cfg.socketMode); err != nil {
_ = ln.Close()
return nil, fmt.Errorf("failed to set mode on socket %s: %w", cfg.addr, err)
@@ -278,10 +254,9 @@ func (h *hostQueryServer) certState() *CertState {
return h.pki.getCertState()
}
// handleHost serves GET /v1/host?addr=<vpn addr>, answering with the identity
// of the host that owns the address: a peer with an active tunnel, or this
// node itself. addr may include a port, which is ignored, so clients can pass
// a connection's remote address through without parsing it.
// handleHost serves GET /v1/host?addr=<vpn addr>, answering with the identity of the host that
// owns the address: a peer with an active tunnel, or this node itself. addr may include a port,
// which is ignored, so clients can pass a connection's remote address through without parsing it
func (h *hostQueryServer) handleHost(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("addr")
if q == "" {
@@ -302,7 +277,7 @@ func (h *hostQueryServer) handleHost(w http.ResponseWriter, r *http.Request) {
h.writeHostIdentity(w, crt)
}
// handleSelf serves GET /v1/self, answering with this node's own identity.
// handleSelf serves GET /v1/self, answering with this node's own identity
func (h *hostQueryServer) handleSelf(w http.ResponseWriter, r *http.Request) {
var crt cert.Certificate
if cs := h.certState(); cs != nil {
@@ -327,10 +302,9 @@ func (h *hostQueryServer) writeHostIdentity(w http.ResponseWriter, crt cert.Cert
}
}
// findCertificateForVpnAddr answers "who owns this vpn address": ourselves
// (from local cert state, since the hostmap never carries an entry for this
// node) or a peer with an active tunnel. Returns nil when the address is
// unknown or the tunnel is mid-teardown.
// findCertificateForVpnAddr answers "who owns this vpn address": ourselves (from local cert state,
// the hostmap never carries an entry for this node) or a peer with an active tunnel. Returns nil
// when the address is unknown or the tunnel is mid-teardown
func findCertificateForVpnAddr(cs *CertState, hostMap *HostMap, ip netip.Addr) cert.Certificate {
if cs != nil && cs.myVpnAddrsTable != nil && cs.myVpnAddrsTable.Contains(ip) {
return cs.getCertificate(cs.initiatingVersion)
@@ -347,8 +321,8 @@ func findCertificateForVpnAddr(cs *CertState, hostMap *HostMap, ip netip.Addr) c
return cc.Certificate
}
// hostIdentity is the JSON document served for both /v1/host and /v1/self.
// Every field is derived from the authenticated certificate alone.
// hostIdentity is the json document served for both /v1/host and /v1/self, every field is derived
// from the authenticated certificate alone
type hostIdentity struct {
Name string `json:"name"`
VpnAddrs []netip.Addr `json:"vpnAddrs"`
@@ -368,8 +342,7 @@ func newHostIdentity(crt cert.Certificate) (hostIdentity, error) {
return hostIdentity{}, err
}
// Slices are always allocated so they marshal as [] rather than null;
// consumers iterate groups without a presence check.
// slices are always allocated so they marshal as [] rather than null
networks := crt.Networks()
id := hostIdentity{
Name: crt.Name(),
@@ -395,10 +368,9 @@ func writeJSONError(w http.ResponseWriter, status int, msg string) {
_ = json.NewEncoder(w).Encode(map[string]string{"error": msg})
}
// parseQueryAddrParam parses the addr query parameter, accepting a bare
// address or an address with a port (`192.168.100.7:54321`, `[fd00::1]:443`)
// so callers can pass a connection's RemoteAddr straight through. The result
// is unmapped: 4in6 addresses (::ffff:a.b.c.d) are normalized to ipv4.
// parseQueryAddrParam parses the addr query parameter, accepting a bare address or an address with
// a port (`192.168.100.7:54321`, `[fd00::1]:443`) so callers can pass a connection's RemoteAddr
// straight through. The result is unmapped, 4in6 addresses (::ffff:a.b.c.d) become ipv4
func parseQueryAddrParam(s string) (netip.Addr, error) {
if ip, err := netip.ParseAddr(s); err == nil {
return ip.Unmap(), nil
@@ -430,7 +402,7 @@ func loadHostQueryConfig(c *config.C) (hostQueryConfig, error) {
cfg.addr = addr
if network == "unix" {
// Read as a string so YAML can't reinterpret the octal literal.
// read as a string so yaml can't reinterpret the octal literal
modeStr := c.GetString("host_query.socket_mode", "0600")
mode, err := strconv.ParseUint(modeStr, 8, 32)
if err != nil || fs.FileMode(mode)&^fs.ModePerm != 0 {
@@ -441,9 +413,8 @@ func loadHostQueryConfig(c *config.C) (hostQueryConfig, error) {
return cfg, nil
}
// parseHostQueryListen splits the host_query.listen config value into a
// network and address for net.Listen: `unix:///abs/path.sock` selects a unix
// socket, anything else must be a tcp host:port.
// parseHostQueryListen splits the host_query.listen config value into a network and address for
// net.Listen: `unix:///abs/path.sock` selects a unix socket, anything else must be a tcp host:port
func parseHostQueryListen(listen string) (network string, addr string, err error) {
if path, ok := strings.CutPrefix(listen, "unix://"); ok {
if !filepath.IsAbs(path) {