Validate requires exactly kea-dhcp4.conf with this agent's control socket and runs kea-dhcp4 -t. Activate writes the file atomically and sends config-reload; on failure the previous file is restored and reloaded. Health uses status-get (pid, uptime, last reload, socket state) and checks that the installed file is the expected revision. Leases pages through lease4-get-page and maps Kea lease states (default, declined, expired-reclaimed, released) to core lease states. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
2.6 KiB
Go
76 lines
2.6 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package dhcp
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Config is the "dhcp:" section of the agent configuration file.
|
|
type Config struct {
|
|
// ConfigFile is the file kea-dhcp4 was started with (-c); config-reload
|
|
// re-reads exactly this file.
|
|
ConfigFile string `yaml:"config_file"`
|
|
// KeaDHCP4 is the program name (looked up in PATH) or an absolute path.
|
|
KeaDHCP4 string `yaml:"kea_dhcp4"`
|
|
// ControlSocket is the UNIX control socket of kea-dhcp4. It must equal
|
|
// the control socket of the server's generator settings.
|
|
ControlSocket string `yaml:"control_socket"`
|
|
// LeasePageSize is the limit of one lease4-get-page command.
|
|
LeasePageSize int `yaml:"lease_page_size"`
|
|
// CommandTimeout bounds every program run and every control command.
|
|
CommandTimeout time.Duration `yaml:"command_timeout"`
|
|
}
|
|
|
|
// DefaultConfig returns the defaults of ADR 0021.
|
|
func DefaultConfig() Config {
|
|
return Config{
|
|
ConfigFile: "/etc/kea/kea-dhcp4.conf",
|
|
KeaDHCP4: "kea-dhcp4",
|
|
ControlSocket: "/run/kea/kea4-ctrl-socket",
|
|
LeasePageSize: 1000,
|
|
CommandTimeout: 60 * time.Second,
|
|
}
|
|
}
|
|
|
|
// maxSocketPath is the usable length of sun_path on Linux (108 bytes
|
|
// including the terminating NUL).
|
|
const maxSocketPath = 107
|
|
|
|
// Validate checks the configuration.
|
|
func (c *Config) Validate() error {
|
|
var errs []error
|
|
if err := checkAbsPath(c.ConfigFile); err != nil {
|
|
errs = append(errs, fmt.Errorf("dhcp.config_file: %w", err))
|
|
}
|
|
if err := checkAbsPath(c.ControlSocket); err != nil {
|
|
errs = append(errs, fmt.Errorf("dhcp.control_socket: %w", err))
|
|
} else if len(c.ControlSocket) > maxSocketPath {
|
|
errs = append(errs, fmt.Errorf("dhcp.control_socket: longer than %d bytes", maxSocketPath))
|
|
}
|
|
if strings.TrimSpace(c.KeaDHCP4) == "" || strings.ContainsAny(c.KeaDHCP4, "\x00\n") {
|
|
errs = append(errs, errors.New("dhcp.kea_dhcp4: program name is empty or invalid"))
|
|
}
|
|
if c.LeasePageSize < 1 || c.LeasePageSize > 100_000 {
|
|
errs = append(errs, fmt.Errorf("dhcp.lease_page_size: %d is out of range [1, 100000]", c.LeasePageSize))
|
|
}
|
|
if c.CommandTimeout <= 0 || c.CommandTimeout > time.Hour {
|
|
errs = append(errs, fmt.Errorf("dhcp.command_timeout: %s is out of range (0, 1h]", c.CommandTimeout))
|
|
}
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
func checkAbsPath(p string) error {
|
|
if p == "" || !path.IsAbs(p) || path.Clean(p) != p || p == "/" {
|
|
return fmt.Errorf("%q must be a clean absolute path", p)
|
|
}
|
|
if strings.ContainsFunc(p, func(r rune) bool { return r < 0x20 || r == 0x7f }) {
|
|
return fmt.Errorf("%q contains a control character", p)
|
|
}
|
|
return nil
|
|
}
|