pxe: move dhcp-host allowlist into a SIGHUP-reloadable file
dnsmasq's SIGHUP re-reads /etc/ethers and any --dhcp-hostsfile= paths,
but NOT dhcp-host= lines from the main conf. Reload() was faithfully
rewriting dnsmasq.conf with the new MAC, sending SIGHUP, and then
dnsmasq kept serving its startup view — so a freshly-registered host
still showed up as "proxy-ignored, tags: eth0" with no "known" tag.
Split the allowlist into ${RuntimeDir}/dhcp-hosts, referenced from the
main conf via dhcp-hostsfile=. writeConf() is static-ish now; Reload
just rewrites the hosts file and SIGHUPs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+41
-12
@@ -120,7 +120,10 @@ func (s *Supervisor) Start(ctx context.Context, hosts []model.Host) error {
|
|||||||
if err := os.MkdirAll(s.cfg.RuntimeDir, 0o755); err != nil {
|
if err := os.MkdirAll(s.cfg.RuntimeDir, 0o755); err != nil {
|
||||||
return fmt.Errorf("mkdir runtime: %w", err)
|
return fmt.Errorf("mkdir runtime: %w", err)
|
||||||
}
|
}
|
||||||
if err := s.writeConf(hosts); err != nil {
|
if err := s.writeHosts(hosts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.writeConf(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
subCtx, cancel := context.WithCancel(ctx)
|
subCtx, cancel := context.WithCancel(ctx)
|
||||||
@@ -152,14 +155,14 @@ func (s *Supervisor) Start(ctx context.Context, hosts []model.Host) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload rewrites the conf with the latest host registry and sends
|
// Reload rewrites the dhcp-hosts allowlist with the latest host
|
||||||
// SIGHUP. It will restart the subprocess if SIGHUP is unsupported
|
// registry and SIGHUPs dnsmasq to pick it up. The main dnsmasq.conf
|
||||||
// (e.g. when running behind an OS that doesn't support it).
|
// is unchanged — it only references the hosts file by path.
|
||||||
func (s *Supervisor) Reload(hosts []model.Host) error {
|
func (s *Supervisor) Reload(hosts []model.Host) error {
|
||||||
if !s.cfg.Enabled {
|
if !s.cfg.Enabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := s.writeConf(hosts); err != nil {
|
if err := s.writeHosts(hosts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
@@ -201,7 +204,7 @@ func (s *Supervisor) Shutdown(timeout time.Duration) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Supervisor) writeConf(hosts []model.Host) error {
|
func (s *Supervisor) writeConf() error {
|
||||||
tmpl, err := template.New("dnsmasq").Parse(dnsmasqTemplate)
|
tmpl, err := template.New("dnsmasq").Parse(dnsmasqTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -219,10 +222,9 @@ func (s *Supervisor) writeConf(hosts []model.Host) error {
|
|||||||
}
|
}
|
||||||
data := struct {
|
data := struct {
|
||||||
Cfg SupervisorConfig
|
Cfg SupervisorConfig
|
||||||
Hosts []model.Host
|
|
||||||
Network string
|
Network string
|
||||||
Netmask string
|
Netmask string
|
||||||
}{s.cfg, hosts, ipnet.IP.String(), net.IP(ipnet.Mask).String()}
|
}{s.cfg, ipnet.IP.String(), net.IP(ipnet.Mask).String()}
|
||||||
if err := tmpl.Execute(f, data); err != nil {
|
if err := tmpl.Execute(f, data); err != nil {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
return fmt.Errorf("render conf: %w", err)
|
return fmt.Errorf("render conf: %w", err)
|
||||||
@@ -240,6 +242,32 @@ func (s *Supervisor) writeConf(hosts []model.Host) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeHosts renders the dhcp-hostsfile referenced by dnsmasq.conf.
|
||||||
|
// Each registered host contributes one line:
|
||||||
|
//
|
||||||
|
// <mac>,set:known
|
||||||
|
//
|
||||||
|
// dnsmasq re-reads this file on SIGHUP — that's the whole point of
|
||||||
|
// keeping it separate from the main conf.
|
||||||
|
func (s *Supervisor) writeHosts(hosts []model.Host) error {
|
||||||
|
if err := os.MkdirAll(s.cfg.RuntimeDir, 0o755); err != nil {
|
||||||
|
return fmt.Errorf("mkdir runtime: %w", err)
|
||||||
|
}
|
||||||
|
path := filepath.Join(s.cfg.RuntimeDir, "dhcp-hosts")
|
||||||
|
tmp := path + ".new"
|
||||||
|
var b strings.Builder
|
||||||
|
for _, h := range hosts {
|
||||||
|
fmt.Fprintf(&b, "%s,set:known\n", h.MAC)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(tmp, []byte(b.String()), 0o644); err != nil {
|
||||||
|
return fmt.Errorf("write dhcp-hosts: %w", err)
|
||||||
|
}
|
||||||
|
if err := os.Rename(tmp, path); err != nil {
|
||||||
|
return fmt.Errorf("rename dhcp-hosts: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Exposed for the UI handlers to show operators what config is live.
|
// Exposed for the UI handlers to show operators what config is live.
|
||||||
func (s *Supervisor) ConfPath() string {
|
func (s *Supervisor) ConfPath() string {
|
||||||
return filepath.Join(s.cfg.RuntimeDir, "dnsmasq.conf")
|
return filepath.Join(s.cfg.RuntimeDir, "dnsmasq.conf")
|
||||||
@@ -275,11 +303,12 @@ no-resolv
|
|||||||
# CIDR — we split Subnet upstream in writeConf().
|
# CIDR — we split Subnet upstream in writeConf().
|
||||||
dhcp-range={{ .Network }},proxy,{{ .Netmask }}
|
dhcp-range={{ .Network }},proxy,{{ .Netmask }}
|
||||||
|
|
||||||
# MAC allowlist: dnsmasq only answers DHCP for MACs with a dhcp-host= below.
|
# MAC allowlist: dnsmasq only answers DHCP for MACs tagged "known".
|
||||||
|
# The per-MAC dhcp-host= entries live in a separate file so SIGHUP
|
||||||
|
# can reload them — dnsmasq does NOT re-read dhcp-host= from the
|
||||||
|
# main conf on SIGHUP, only from dhcp-hostsfile=.
|
||||||
dhcp-ignore=tag:!known
|
dhcp-ignore=tag:!known
|
||||||
{{- range .Hosts }}
|
dhcp-hostsfile={{ .Cfg.RuntimeDir }}/dhcp-hosts
|
||||||
dhcp-host={{ .MAC }},set:known
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
# Keep runtime state inside RuntimeDir so the systemd sandbox
|
# Keep runtime state inside RuntimeDir so the systemd sandbox
|
||||||
# (ReadWritePaths=/var/lib/vetting ...) doesn't block writes to the
|
# (ReadWritePaths=/var/lib/vetting ...) doesn't block writes to the
|
||||||
|
|||||||
Reference in New Issue
Block a user